summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java')
-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.java19
-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.java46
-rw-r--r--core/java/android/app/ActivityView.java2
-rw-r--r--core/java/android/app/ApplicationThreadNative.java45
-rw-r--r--core/java/android/app/ContextImpl.java29
-rw-r--r--core/java/android/app/DatePickerDialog.java3
-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.java15
-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.java91
-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.java2
-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.java145
-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.java94
-rw-r--r--core/java/android/hardware/camera2/CameraManager.java146
-rw-r--r--core/java/android/hardware/camera2/CameraMetadata.java88
-rw-r--r--core/java/android/hardware/camera2/CaptureRequest.java82
-rw-r--r--core/java/android/hardware/camera2/CaptureResult.java82
-rw-r--r--core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java58
-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/SSLCertificateSocketFactory.java2
-rw-r--r--core/java/android/net/Uri.java9
-rw-r--r--core/java/android/os/Build.java48
-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/provider/Contacts.java49
-rw-r--r--core/java/android/provider/ContactsContract.java272
-rw-r--r--core/java/android/provider/Settings.java15
-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/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/RenderNode.java2
-rw-r--r--core/java/android/view/Surface.java1
-rw-r--r--core/java/android/view/View.java468
-rw-r--r--core/java/android/view/ViewGroup.java13
-rw-r--r--core/java/android/view/ViewRootImpl.java13
-rw-r--r--core/java/android/view/ViewStub.java25
-rw-r--r--core/java/android/view/WindowManager.java29
-rw-r--r--core/java/android/view/WindowManagerGlobal.java8
-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.java61
-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.java43
-rw-r--r--core/java/android/widget/AbsSeekBar.java15
-rw-r--r--core/java/android/widget/AbsSpinner.java19
-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.java18
-rw-r--r--core/java/android/widget/AdapterViewAnimator.java10
-rw-r--r--core/java/android/widget/AdapterViewFlipper.java10
-rw-r--r--core/java/android/widget/ArrayAdapter.java71
-rw-r--r--core/java/android/widget/Button.java10
-rw-r--r--core/java/android/widget/CalendarView.java6
-rw-r--r--core/java/android/widget/CheckBox.java10
-rw-r--r--core/java/android/widget/CheckedTextView.java10
-rw-r--r--core/java/android/widget/Chronometer.java10
-rw-r--r--core/java/android/widget/CompoundButton.java10
-rw-r--r--core/java/android/widget/CursorAdapter.java46
-rw-r--r--core/java/android/widget/DatePicker.java18
-rw-r--r--core/java/android/widget/DatePickerCalendarDelegate.java41
-rw-r--r--core/java/android/widget/DayPickerView.java21
-rw-r--r--core/java/android/widget/DigitalClock.java10
-rw-r--r--core/java/android/widget/EditText.java15
-rw-r--r--core/java/android/widget/Editor.java2
-rw-r--r--core/java/android/widget/ExpandableListView.java10
-rw-r--r--core/java/android/widget/FrameLayout.java25
-rw-r--r--core/java/android/widget/Gallery.java15
-rw-r--r--core/java/android/widget/GridLayout.java10
-rw-r--r--core/java/android/widget/GridView.java10
-rw-r--r--core/java/android/widget/HorizontalScrollView.java15
-rw-r--r--core/java/android/widget/ImageButton.java10
-rw-r--r--core/java/android/widget/ImageSwitcher.java10
-rw-r--r--core/java/android/widget/ImageView.java15
-rw-r--r--core/java/android/widget/LinearLayout.java10
-rw-r--r--core/java/android/widget/ListView.java10
-rw-r--r--core/java/android/widget/MediaController.java10
-rw-r--r--core/java/android/widget/MultiAutoCompleteTextView.java10
-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.java10
-rw-r--r--core/java/android/widget/QuickContactBadge.java10
-rw-r--r--core/java/android/widget/RadialTimePickerView.java618
-rw-r--r--core/java/android/widget/RadioButton.java10
-rw-r--r--core/java/android/widget/RadioGroup.java10
-rw-r--r--core/java/android/widget/RatingBar.java10
-rw-r--r--core/java/android/widget/RelativeLayout.java116
-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.java15
-rw-r--r--core/java/android/widget/SearchView.java10
-rw-r--r--core/java/android/widget/SeekBar.java10
-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.java10
-rw-r--r--core/java/android/widget/Spinner.java228
-rw-r--r--core/java/android/widget/SpinnerAdapter.java13
-rw-r--r--core/java/android/widget/StackView.java15
-rw-r--r--core/java/android/widget/SuggestionsAdapter.java39
-rw-r--r--core/java/android/widget/Switch.java187
-rw-r--r--core/java/android/widget/TabHost.java13
-rw-r--r--core/java/android/widget/TabWidget.java18
-rw-r--r--core/java/android/widget/TableLayout.java10
-rw-r--r--core/java/android/widget/TableRow.java10
-rw-r--r--core/java/android/widget/TextSwitcher.java10
-rw-r--r--core/java/android/widget/TextView.java446
-rw-r--r--core/java/android/widget/TextViewWithCircularIndicator.java43
-rw-r--r--core/java/android/widget/TimePicker.java18
-rw-r--r--core/java/android/widget/ToggleButton.java10
-rw-r--r--core/java/android/widget/TwoLineListItem.java10
-rw-r--r--core/java/android/widget/VideoView.java10
-rw-r--r--core/java/android/widget/ViewAnimator.java10
-rw-r--r--core/java/android/widget/ViewFlipper.java10
-rw-r--r--core/java/android/widget/ViewSwitcher.java10
-rw-r--r--core/java/android/widget/YearPickerView.java45
-rw-r--r--core/java/android/widget/ZoomButton.java10
-rw-r--r--core/java/android/widget/ZoomControls.java10
-rw-r--r--core/java/com/android/internal/app/ResolverActivity.java2
-rw-r--r--core/java/com/android/internal/transition/EpicenterClipReveal.java115
-rw-r--r--core/java/com/android/internal/util/UserIcons.java9
-rw-r--r--core/java/com/android/internal/view/menu/ActionMenuItemView.java6
-rw-r--r--core/java/com/android/internal/view/menu/ListMenuItemView.java4
-rw-r--r--core/java/com/android/internal/view/menu/MenuPopupHelper.java6
-rw-r--r--core/java/com/android/internal/widget/AccessibleDateAnimator.java4
-rw-r--r--core/java/com/android/internal/widget/ActionBarContextView.java4
-rw-r--r--core/java/com/android/internal/widget/ActionBarView.java6
-rw-r--r--core/java/com/android/internal/widget/FaceUnlockView.java67
-rw-r--r--core/java/com/android/internal/widget/ILockSettings.aidl1
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java946
-rw-r--r--core/java/com/android/internal/widget/LockPatternView.java41
-rw-r--r--core/java/com/android/internal/widget/ScrollingTabContainerView.java43
-rw-r--r--core/java/com/android/internal/widget/SizeAdaptiveLayout.java442
-rw-r--r--core/java/com/android/internal/widget/WaveView.java663
-rw-r--r--core/java/com/android/internal/widget/multiwaveview/Ease.java132
-rw-r--r--core/java/com/android/internal/widget/multiwaveview/GlowPadView.java1383
-rw-r--r--core/java/com/android/internal/widget/multiwaveview/PointCloud.java225
-rw-r--r--core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java229
-rw-r--r--core/java/com/android/internal/widget/multiwaveview/Tweener.java177
-rw-r--r--core/java/com/android/server/backup/AccountSyncSettingsBackupHelper.java380
-rw-r--r--core/java/com/android/server/backup/SystemBackupAgent.java4
213 files changed, 8805 insertions, 8253 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..a09bca1 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -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
@@ -1242,22 +1242,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;
}
@@ -4619,7 +4615,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);
}
/**
@@ -6080,6 +6076,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 fb2bcd0..de3c95c 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,7 @@ public final class ActivityThread {
boolean hideForNow;
Configuration newConfig;
Configuration createdConfig;
+ Configuration overrideConfig;
ActivityClientRecord nextIdle;
ProfilerInfo profilerInfo;
@@ -618,12 +616,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 +646,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) {
@@ -890,6 +892,7 @@ public final class ActivityThread {
sendMessage(H.LOW_MEMORY, null);
}
+ @Override
public void scheduleActivityConfigurationChanged(IBinder token) {
sendMessage(H.ACTIVITY_CONFIGURATION_CHANGED, token);
}
@@ -1671,7 +1674,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 +2359,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 +3004,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;
@@ -3270,7 +3274,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(
@@ -3564,7 +3568,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 +3671,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 +3812,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 +3861,9 @@ public final class ActivityThread {
if (config != null) {
target.createdConfig = config;
}
+ if (overrideConfig != null) {
+ target.overrideConfig = overrideConfig;
+ }
target.pendingConfigChanges |= configChanges;
}
}
@@ -3969,6 +3976,7 @@ public final class ActivityThread {
}
}
r.startsNotResumed = tmp.startsNotResumed;
+ r.overrideConfig = tmp.overrideConfig;
handleLaunchActivity(r, currentIntent);
}
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..349b66d 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);
@@ -775,11 +780,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 +792,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 +821,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 +830,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);
}
@@ -1112,8 +1124,7 @@ class ApplicationThreadProxy implements IApplicationThread {
data.recycle();
}
- public final void scheduleActivityConfigurationChanged(
- IBinder token) throws RemoteException {
+ public final void scheduleActivityConfigurationChanged(IBinder token) throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
data.writeStrongBinder(token);
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 2ef046d..39caf0b 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -80,6 +80,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 +94,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;
@@ -603,6 +607,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 +779,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) {
@@ -2247,11 +2264,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,
@@ -2286,15 +2302,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/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..f2c912e 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;
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 1ed709b..ecf25fc 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);
}
@@ -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..37a46be 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2808,14 +2808,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
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 d7d9e8b..04777ba 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;
@@ -2759,9 +2760,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.
@@ -2946,20 +2955,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>";
@@ -2972,22 +2980,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);
@@ -3001,8 +3005,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 {
@@ -3010,140 +3013,111 @@ 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,
+ 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);
} 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: "
@@ -3153,11 +3127,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/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..bb45b91 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>
- * </tr>
- * <tr>
- * <td align="left">RAW_OPAQUE</td>
- * <td align="left">RAW16</td>
- * <td align="left">RAW</td>
+ * <td align="left">OPAQUE_REPROCESSING</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 =
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..af7d365 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
//
@@ -432,23 +439,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 +532,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
//
@@ -1823,6 +1886,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
//
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 0849df8..e1b14cc 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> -
@@ -1154,7 +1160,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>
@@ -1239,10 +1245,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>
@@ -1250,9 +1252,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
@@ -1372,6 +1377,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>
@@ -1387,6 +1396,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
@@ -1751,18 +1761,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>
@@ -1773,9 +1793,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 =
@@ -2416,6 +2438,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 1396940..5bf5b29 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> -
@@ -1627,7 +1633,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>
@@ -1855,10 +1861,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>
@@ -1866,9 +1868,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
@@ -1988,6 +1993,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>
@@ -2003,6 +2012,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
@@ -2465,18 +2475,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>
@@ -2487,9 +2507,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 =
@@ -3647,6 +3669,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/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/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..e4b594a 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");
@@ -653,6 +658,7 @@ public class Build {
public static boolean isFingerprintConsistent() {
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");
if (TextUtils.isEmpty(system)) {
Slog.e(TAG, "Required ro.build.fingerprint is empty!");
@@ -667,6 +673,14 @@ 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;
+ }
+ }
+
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/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 3e75460..dddbe78 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -132,7 +132,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 +153,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 +306,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 +989,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";
@@ -3657,6 +3653,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 +3665,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 +3708,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 +3722,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 +3730,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";
@@ -5101,6 +5105,7 @@ public final class Settings {
* Whether Theater Mode is on.
* {@hide}
*/
+ @SystemApi
public static final String THEATER_MODE_ON = "theater_mode_on";
/**
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 74b7b69..5b07397 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/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/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 e4a8978..bac087e 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;
@@ -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;
@@ -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,8 +5425,10 @@ 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.setPackageName(getContext().getPackageName());
@@ -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);
@@ -5620,8 +5603,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @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);
@@ -6691,7 +6676,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 +7378,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 +7665,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 +8067,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()
@@ -8728,20 +8712,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);
+ }
}
/**
@@ -8864,7 +8856,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.
*
@@ -9871,9 +9863,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 {
/**
@@ -10533,7 +10531,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.
*
@@ -11581,7 +11579,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>
*
@@ -11620,7 +11618,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>
*
@@ -11628,7 +11626,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
*
@@ -11648,6 +11646,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()) {
@@ -12533,7 +12533,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
*/
@@ -13385,7 +13385,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
@@ -13786,7 +13786,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
@@ -13827,7 +13827,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},
@@ -13895,7 +13895,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
@@ -14799,10 +14799,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) {
@@ -15137,7 +15136,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) {
@@ -15960,7 +15959,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);
}
/**
@@ -15975,13 +15974,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);
}
}
@@ -16079,26 +16087,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) {
@@ -16186,6 +16198,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.
@@ -16648,8 +16673,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
*
@@ -17273,7 +17298,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.
@@ -17571,7 +17596,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>
*
@@ -17684,37 +17709,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);
}
/**
@@ -18177,7 +18205,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/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 719e780..879fe19 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;
}
@@ -2650,8 +2652,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);
@@ -2678,8 +2681,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 (mAttachInfo != null) {
final ArrayList<View> childrenForAccessibility = mAttachInfo.mTempArrayList;
@@ -2694,8 +2698,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());
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e4d82b1..f78c018 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -472,8 +472,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();
@@ -760,6 +762,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 +789,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 +1330,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/WindowManager.java b/core/java/android/view/WindowManager.java
index 094a8a1..740cb5d 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -501,13 +501,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
@@ -1332,6 +1325,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
@@ -1628,6 +1631,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);
}
@@ -1676,6 +1680,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();
}
@@ -1858,6 +1863,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;
@@ -1966,8 +1976,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..ed17e3f 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -213,15 +213,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 b8e94ee..ff1fde7 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -579,13 +579,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..4a7cc6d 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,25 @@ public class WebView extends AbsoluteLayout
return mProvider.getViewDelegate().shouldDelayChildPressedState();
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(WebView.class.getName());
mProvider.getViewDelegate().onInitializeAccessibilityNodeInfo(info);
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(WebView.class.getName());
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..897749f 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,18 +1489,20 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mLastAccessibilityScrollEventToIndex = lastVisiblePosition;
}
}
- super.sendAccessibilityEvent(eventType);
+ super.sendAccessibilityEventInternal(eventType);
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(AbsListView.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(AbsListView.class.getName());
if (isEnabled()) {
if (canScrollUp()) {
@@ -1522,9 +1531,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 +2795,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 +2808,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 +2820,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..1f7be63 100644
--- a/core/java/android/widget/AbsSeekBar.java
+++ b/core/java/android/widget/AbsSeekBar.java
@@ -715,15 +715,17 @@ public abstract class AbsSeekBar extends ProgressBar {
return super.onKeyDown(keyCode, event);
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(AbsSeekBar.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(AbsSeekBar.class.getName());
if (isEnabled()) {
@@ -737,9 +739,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..5d8d48f 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);
}
@@ -471,15 +470,17 @@ public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> {
}
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(AbsSpinner.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(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..428b6ce 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);
@@ -953,9 +955,10 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
return false;
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(AdapterView.class.getName());
info.setScrollable(isScrollableForAccessibility());
View selectedView = getSelectedView();
@@ -964,9 +967,10 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
}
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(AdapterView.class.getName());
event.setScrollable(isScrollableForAccessibility());
View selectedView = getSelectedView();
diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java
index 1bc2f4b..96eb0e2 100644
--- a/core/java/android/widget/AdapterViewAnimator.java
+++ b/core/java/android/widget/AdapterViewAnimator.java
@@ -1084,15 +1084,17 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
public void fyiWillBeAdvancedByHostKThx() {
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(AdapterViewAnimator.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(AdapterViewAnimator.class.getName());
}
}
diff --git a/core/java/android/widget/AdapterViewFlipper.java b/core/java/android/widget/AdapterViewFlipper.java
index 285dee8..a7ba617 100644
--- a/core/java/android/widget/AdapterViewFlipper.java
+++ b/core/java/android/widget/AdapterViewFlipper.java
@@ -304,15 +304,17 @@ public class AdapterViewFlipper extends AdapterViewAnimator {
updateRunning(false);
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(AdapterViewFlipper.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(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..90d77f9 100644
--- a/core/java/android/widget/Button.java
+++ b/core/java/android/widget/Button.java
@@ -111,15 +111,17 @@ public class Button extends TextView {
super(context, attrs, defStyleAttr, defStyleRes);
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(Button.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(Button.class.getName());
}
}
diff --git a/core/java/android/widget/CalendarView.java b/core/java/android/widget/CalendarView.java
index ed59ea6..d58da8f 100644
--- a/core/java/android/widget/CalendarView.java
+++ b/core/java/android/widget/CalendarView.java
@@ -501,13 +501,15 @@ public class CalendarView extends FrameLayout {
mDelegate.onConfigurationChanged(newConfig);
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
event.setClassName(CalendarView.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
info.setClassName(CalendarView.class.getName());
}
diff --git a/core/java/android/widget/CheckBox.java b/core/java/android/widget/CheckBox.java
index 71438c9..b1fb338 100644
--- a/core/java/android/widget/CheckBox.java
+++ b/core/java/android/widget/CheckBox.java
@@ -72,15 +72,17 @@ public class CheckBox extends CompoundButton {
super(context, attrs, defStyleAttr, defStyleRes);
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(CheckBox.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(CheckBox.class.getName());
}
}
diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java
index 69969a9..477862b 100644
--- a/core/java/android/widget/CheckedTextView.java
+++ b/core/java/android/widget/CheckedTextView.java
@@ -435,16 +435,18 @@ public class CheckedTextView extends TextView implements Checkable {
}
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(CheckedTextView.class.getName());
event.setChecked(mChecked);
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(CheckedTextView.class.getName());
info.setCheckable(true);
info.setChecked(mChecked);
diff --git a/core/java/android/widget/Chronometer.java b/core/java/android/widget/Chronometer.java
index f94789d..05fc6a1 100644
--- a/core/java/android/widget/Chronometer.java
+++ b/core/java/android/widget/Chronometer.java
@@ -281,15 +281,17 @@ public class Chronometer extends TextView {
}
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(Chronometer.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(Chronometer.class.getName());
}
}
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
index 447ccc2..41a3915 100644
--- a/core/java/android/widget/CompoundButton.java
+++ b/core/java/android/widget/CompoundButton.java
@@ -324,16 +324,18 @@ public abstract class CompoundButton extends Button implements Checkable {
}
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(CompoundButton.class.getName());
event.setChecked(mChecked);
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(CompoundButton.class.getName());
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..0f0cd2e 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -283,26 +283,30 @@ 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);
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
mDelegate.onInitializeAccessibilityEvent(event);
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
mDelegate.onInitializeAccessibilityNodeInfo(info);
}
diff --git a/core/java/android/widget/DatePickerCalendarDelegate.java b/core/java/android/widget/DatePickerCalendarDelegate.java
index e75643ab..a3b834e 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);
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..372bdb3 100644
--- a/core/java/android/widget/DigitalClock.java
+++ b/core/java/android/widget/DigitalClock.java
@@ -115,16 +115,18 @@ public class DigitalClock extends TextView {
}
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
//noinspection deprecation
event.setClassName(DigitalClock.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
//noinspection deprecation
info.setClassName(DigitalClock.class.getName());
}
diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java
index a8ff562..f54beb5 100644
--- a/core/java/android/widget/EditText.java
+++ b/core/java/android/widget/EditText.java
@@ -122,20 +122,23 @@ public class EditText extends TextView {
super.setEllipsize(ellipsis);
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(EditText.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(EditText.class.getName());
}
+ /** @hide */
@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 +150,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..675dc9b 100644
--- a/core/java/android/widget/ExpandableListView.java
+++ b/core/java/android/widget/ExpandableListView.java
@@ -1341,15 +1341,17 @@ public class ExpandableListView extends ListView {
}
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(ExpandableListView.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(ExpandableListView.class.getName());
}
}
diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java
index d974c29..b5782fc 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);
+ }
}
}
@@ -704,15 +709,17 @@ public class FrameLayout extends ViewGroup {
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(FrameLayout.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(FrameLayout.class.getName());
}
diff --git a/core/java/android/widget/Gallery.java b/core/java/android/widget/Gallery.java
index f7c839f..3c428b0 100644
--- a/core/java/android/widget/Gallery.java
+++ b/core/java/android/widget/Gallery.java
@@ -1373,15 +1373,17 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(Gallery.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(Gallery.class.getName());
info.setScrollable(mItemCount > 1);
if (isEnabled()) {
@@ -1394,9 +1396,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..cc925eb 100644
--- a/core/java/android/widget/GridLayout.java
+++ b/core/java/android/widget/GridLayout.java
@@ -1190,15 +1190,17 @@ public class GridLayout extends ViewGroup {
}
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(GridLayout.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(GridLayout.class.getName());
}
diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java
index efd6fc0..f7ce57b 100644
--- a/core/java/android/widget/GridView.java
+++ b/core/java/android/widget/GridView.java
@@ -2341,15 +2341,17 @@ public class GridView extends AbsListView {
return result;
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(GridView.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(GridView.class.getName());
final int columnsCount = getNumColumns();
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index 1b93b97..f1fa1b6 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) {
@@ -794,9 +795,10 @@ public class HorizontalScrollView extends FrameLayout {
return false;
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(HorizontalScrollView.class.getName());
final int scrollRange = getScrollRange();
if (scrollRange > 0) {
@@ -810,9 +812,10 @@ public class HorizontalScrollView extends FrameLayout {
}
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(HorizontalScrollView.class.getName());
event.setScrollable(getScrollRange() > 0);
event.setScrollX(mScrollX);
diff --git a/core/java/android/widget/ImageButton.java b/core/java/android/widget/ImageButton.java
index 3a20628..3b6825d 100644
--- a/core/java/android/widget/ImageButton.java
+++ b/core/java/android/widget/ImageButton.java
@@ -92,15 +92,17 @@ public class ImageButton extends ImageView {
return false;
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(ImageButton.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(ImageButton.class.getName());
}
}
diff --git a/core/java/android/widget/ImageSwitcher.java b/core/java/android/widget/ImageSwitcher.java
index c048970..0910eb0 100644
--- a/core/java/android/widget/ImageSwitcher.java
+++ b/core/java/android/widget/ImageSwitcher.java
@@ -56,15 +56,17 @@ public class ImageSwitcher extends ViewSwitcher
showNext();
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(ImageSwitcher.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(ImageSwitcher.class.getName());
}
}
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index c68bfca..fbad314 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -239,9 +239,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);
@@ -1418,15 +1419,17 @@ public class ImageView extends View {
}
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(ImageView.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(ImageView.class.getName());
}
}
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
index 6476cdc..28e50c4 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -1806,15 +1806,17 @@ public class LinearLayout extends ViewGroup {
return p instanceof LinearLayout.LayoutParams;
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(LinearLayout.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(LinearLayout.class.getName());
}
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index ba6f061..df0d1fd 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -3878,15 +3878,17 @@ public class ListView extends AbsListView {
return false;
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(ListView.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(ListView.class.getName());
final int rowsCount = getCount();
diff --git a/core/java/android/widget/MediaController.java b/core/java/android/widget/MediaController.java
index f1aaa4d..4cafe72 100644
--- a/core/java/android/widget/MediaController.java
+++ b/core/java/android/widget/MediaController.java
@@ -625,15 +625,17 @@ public class MediaController extends FrameLayout {
super.setEnabled(enabled);
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(MediaController.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(MediaController.class.getName());
}
diff --git a/core/java/android/widget/MultiAutoCompleteTextView.java b/core/java/android/widget/MultiAutoCompleteTextView.java
index cbd01b0..6954eea 100644
--- a/core/java/android/widget/MultiAutoCompleteTextView.java
+++ b/core/java/android/widget/MultiAutoCompleteTextView.java
@@ -202,15 +202,17 @@ public class MultiAutoCompleteTextView extends AutoCompleteTextView {
editable.replace(start, end, mTokenizer.terminateToken(text));
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(MultiAutoCompleteTextView.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(MultiAutoCompleteTextView.class.getName());
}
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..d32cb10 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -1825,17 +1825,19 @@ public class ProgressBar extends View {
mAttached = false;
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(ProgressBar.class.getName());
event.setItemCount(mMax);
event.setCurrentItemIndex(mProgress);
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(ProgressBar.class.getName());
}
diff --git a/core/java/android/widget/QuickContactBadge.java b/core/java/android/widget/QuickContactBadge.java
index 23fa402..d0e8081 100644
--- a/core/java/android/widget/QuickContactBadge.java
+++ b/core/java/android/widget/QuickContactBadge.java
@@ -304,15 +304,17 @@ public class QuickContactBadge extends ImageView implements OnClickListener {
}
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(QuickContactBadge.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(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..82280b4 100644
--- a/core/java/android/widget/RadioButton.java
+++ b/core/java/android/widget/RadioButton.java
@@ -79,15 +79,17 @@ public class RadioButton extends CompoundButton {
}
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(RadioButton.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(RadioButton.class.getName());
}
}
diff --git a/core/java/android/widget/RadioGroup.java b/core/java/android/widget/RadioGroup.java
index 78d05b0..c0f60eb 100644
--- a/core/java/android/widget/RadioGroup.java
+++ b/core/java/android/widget/RadioGroup.java
@@ -240,15 +240,17 @@ public class RadioGroup extends LinearLayout {
return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(RadioGroup.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(RadioGroup.class.getName());
}
diff --git a/core/java/android/widget/RatingBar.java b/core/java/android/widget/RatingBar.java
index 82b490e..2d0649d 100644
--- a/core/java/android/widget/RatingBar.java
+++ b/core/java/android/widget/RatingBar.java
@@ -331,15 +331,17 @@ public class RatingBar extends AbsSeekBar {
super.setMax(max);
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(RatingBar.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(RatingBar.class.getName());
}
}
diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java
index 5b604cd..71f4da0 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());
}
@@ -1127,15 +1103,17 @@ public class RelativeLayout extends ViewGroup {
return false;
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(RelativeLayout.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(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..1098419 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()) {
@@ -838,9 +839,10 @@ public class ScrollView extends FrameLayout {
return false;
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(ScrollView.class.getName());
if (isEnabled()) {
final int scrollRange = getScrollRange();
@@ -856,9 +858,10 @@ public class ScrollView extends FrameLayout {
}
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(ScrollView.class.getName());
final boolean scrollable = getScrollRange() > 0;
event.setScrollable(scrollable);
diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java
index 4ee6418..a1f361a 100644
--- a/core/java/android/widget/SearchView.java
+++ b/core/java/android/widget/SearchView.java
@@ -1325,15 +1325,17 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
setIconified(false);
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(SearchView.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(SearchView.class.getName());
}
diff --git a/core/java/android/widget/SeekBar.java b/core/java/android/widget/SeekBar.java
index dc7c04c..aa5c055 100644
--- a/core/java/android/widget/SeekBar.java
+++ b/core/java/android/widget/SeekBar.java
@@ -124,15 +124,17 @@ public class SeekBar extends AbsSeekBar {
}
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(SeekBar.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(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..e3f4a10 100644
--- a/core/java/android/widget/SlidingDrawer.java
+++ b/core/java/android/widget/SlidingDrawer.java
@@ -837,15 +837,17 @@ public class SlidingDrawer extends ViewGroup {
}
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(SlidingDrawer.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(SlidingDrawer.class.getName());
}
diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java
index 0d76239..a6e0e6d 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
@@ -692,15 +765,17 @@ public class Spinner extends AbsSpinner implements OnClickListener {
dialog.dismiss();
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(Spinner.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(Spinner.class.getName());
if (mAdapter != null) {
@@ -847,14 +922,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 +1057,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 +1081,7 @@ public class Spinner extends AbsSpinner implements OnClickListener {
public void setPromptText(CharSequence hintText) {
mPrompt = hintText;
}
-
+
public CharSequence getHintText() {
return mPrompt;
}
@@ -1003,7 +1090,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 +1266,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..2aa1c09 100644
--- a/core/java/android/widget/StackView.java
+++ b/core/java/android/widget/StackView.java
@@ -1224,15 +1224,17 @@ public class StackView extends AdapterViewAnimator {
measureChildren();
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(StackView.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(StackView.class.getName());
info.setScrollable(getChildCount() > 1);
if (isEnabled()) {
@@ -1245,9 +1247,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..a282cf5 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,6 +25,7 @@ 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;
@@ -84,7 +86,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 +485,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 +608,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 +837,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) {
@@ -1180,15 +1353,17 @@ public class Switch extends CompoundButton {
}
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(Switch.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(Switch.class.getName());
CharSequence switchText = isChecked() ? mTextOn : mTextOff;
if (!TextUtils.isEmpty(switchText)) {
diff --git a/core/java/android/widget/TabHost.java b/core/java/android/widget/TabHost.java
index 89df51a..3c3389a 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 */
}
@@ -383,15 +384,17 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
}
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(TabHost.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(TabHost.class.getName());
}
diff --git a/core/java/android/widget/TabWidget.java b/core/java/android/widget/TabWidget.java
index 47a5449..bddee91 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) {
@@ -414,28 +415,31 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
return false;
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(TabWidget.class.getName());
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);
+ super.sendAccessibilityEventUncheckedInternal(event);
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(TabWidget.class.getName());
}
diff --git a/core/java/android/widget/TableLayout.java b/core/java/android/widget/TableLayout.java
index f4b2ce0..a6b78a4 100644
--- a/core/java/android/widget/TableLayout.java
+++ b/core/java/android/widget/TableLayout.java
@@ -666,15 +666,17 @@ public class TableLayout extends LinearLayout {
return new LayoutParams(p);
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(TableLayout.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(TableLayout.class.getName());
}
diff --git a/core/java/android/widget/TableRow.java b/core/java/android/widget/TableRow.java
index fe3631a..c29296a 100644
--- a/core/java/android/widget/TableRow.java
+++ b/core/java/android/widget/TableRow.java
@@ -379,15 +379,17 @@ public class TableRow extends LinearLayout {
return new LayoutParams(p);
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(TableRow.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(TableRow.class.getName());
}
diff --git a/core/java/android/widget/TextSwitcher.java b/core/java/android/widget/TextSwitcher.java
index 1aefd2b..7c883ba 100644
--- a/core/java/android/widget/TextSwitcher.java
+++ b/core/java/android/widget/TextSwitcher.java
@@ -91,15 +91,17 @@ public class TextSwitcher extends ViewSwitcher {
((TextView)getCurrentView()).setText(text);
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(TextSwitcher.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(TextSwitcher.class.getName());
}
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index dd8280b..94fc9e9 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;
@@ -214,6 +216,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 +302,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 +315,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 +358,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 +380,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 +393,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 +409,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 +422,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 +440,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 +458,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 +470,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 +527,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 +779,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 +866,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 +1258,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 +1459,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
resetResolvedDrawables();
resolveDrawables();
+ applyCompoundDrawableTint();
}
}
@@ -1783,7 +1818,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 +1831,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 +1844,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 +1857,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 +2055,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 +2074,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 +2148,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
resetResolvedDrawables();
resolveDrawables();
+ applyCompoundDrawableTint();
invalidate();
requestLayout();
}
@@ -2198,10 +2232,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 +2257,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 +2287,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 +2426,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 +2445,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 +2487,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 +2747,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 +3826,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 +3840,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 +5176,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 +5189,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 +5211,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 +5219,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 +5227,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 +5235,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 +5439,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 +8507,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()) {
@@ -8404,9 +8531,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
UserHandle.USER_CURRENT_OR_SELF) == 1);
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(TextView.class.getName());
final boolean isPassword = hasPasswordTransformationMethod();
@@ -8419,9 +8547,10 @@ 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();
@@ -8579,15 +8708,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..bbf5f53 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -195,26 +195,30 @@ public class TimePicker extends FrameLayout {
mDelegate.onRestoreInstanceState(ss);
}
+ /** @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);
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
mDelegate.onInitializeAccessibilityEvent(event);
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
mDelegate.onInitializeAccessibilityNodeInfo(info);
}
diff --git a/core/java/android/widget/ToggleButton.java b/core/java/android/widget/ToggleButton.java
index 28519d1..1b23778 100644
--- a/core/java/android/widget/ToggleButton.java
+++ b/core/java/android/widget/ToggleButton.java
@@ -153,15 +153,17 @@ public class ToggleButton extends CompoundButton {
}
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(ToggleButton.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(ToggleButton.class.getName());
}
}
diff --git a/core/java/android/widget/TwoLineListItem.java b/core/java/android/widget/TwoLineListItem.java
index 5606c60..9035dbe 100644
--- a/core/java/android/widget/TwoLineListItem.java
+++ b/core/java/android/widget/TwoLineListItem.java
@@ -93,15 +93,17 @@ public class TwoLineListItem extends RelativeLayout {
return mText2;
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(TwoLineListItem.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(TwoLineListItem.class.getName());
}
}
diff --git a/core/java/android/widget/VideoView.java b/core/java/android/widget/VideoView.java
index 572cca2..a240dc2 100644
--- a/core/java/android/widget/VideoView.java
+++ b/core/java/android/widget/VideoView.java
@@ -201,15 +201,17 @@ public class VideoView extends SurfaceView
setMeasuredDimension(width, height);
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(VideoView.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(VideoView.class.getName());
}
diff --git a/core/java/android/widget/ViewAnimator.java b/core/java/android/widget/ViewAnimator.java
index eee914e..d31754b 100644
--- a/core/java/android/widget/ViewAnimator.java
+++ b/core/java/android/widget/ViewAnimator.java
@@ -357,15 +357,17 @@ public class ViewAnimator extends FrameLayout {
return (getCurrentView() != null) ? getCurrentView().getBaseline() : super.getBaseline();
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(ViewAnimator.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(ViewAnimator.class.getName());
}
}
diff --git a/core/java/android/widget/ViewFlipper.java b/core/java/android/widget/ViewFlipper.java
index cf1f554..587f469 100644
--- a/core/java/android/widget/ViewFlipper.java
+++ b/core/java/android/widget/ViewFlipper.java
@@ -149,15 +149,17 @@ public class ViewFlipper extends ViewAnimator {
updateRunning();
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(ViewFlipper.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(ViewFlipper.class.getName());
}
diff --git a/core/java/android/widget/ViewSwitcher.java b/core/java/android/widget/ViewSwitcher.java
index 0376918..c97770f 100644
--- a/core/java/android/widget/ViewSwitcher.java
+++ b/core/java/android/widget/ViewSwitcher.java
@@ -68,15 +68,17 @@ public class ViewSwitcher extends ViewAnimator {
super.addView(child, index, params);
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(ViewSwitcher.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(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..e0be0ab 100644
--- a/core/java/android/widget/ZoomButton.java
+++ b/core/java/android/widget/ZoomButton.java
@@ -103,15 +103,17 @@ public class ZoomButton extends ImageButton implements OnLongClickListener {
return super.dispatchUnhandledMove(focused, direction);
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(ZoomButton.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(ZoomButton.class.getName());
}
}
diff --git a/core/java/android/widget/ZoomControls.java b/core/java/android/widget/ZoomControls.java
index 8897875..a0aacea 100644
--- a/core/java/android/widget/ZoomControls.java
+++ b/core/java/android/widget/ZoomControls.java
@@ -109,15 +109,17 @@ public class ZoomControls extends LinearLayout {
return mZoomIn.hasFocus() || mZoomOut.hasFocus();
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(ZoomControls.class.getName());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(ZoomControls.class.getName());
}
}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 649a59f..144cc33 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -841,6 +841,8 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
Log.d(TAG, "Error calling setLastChosenActivity\n" + re);
}
+ // Clear the value of mOtherProfile from previous call.
+ mOtherProfile = null;
mList.clear();
if (mBaseResolveList != null) {
currentResolveList = mOrigResolveList = mBaseResolveList;
diff --git a/core/java/com/android/internal/transition/EpicenterClipReveal.java b/core/java/com/android/internal/transition/EpicenterClipReveal.java
new file mode 100644
index 0000000..d8a7f16
--- /dev/null
+++ b/core/java/com/android/internal/transition/EpicenterClipReveal.java
@@ -0,0 +1,115 @@
+/*
+ * 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 com.android.internal.transition;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.animation.RectEvaluator;
+import android.content.Context;
+import android.graphics.Rect;
+import android.transition.TransitionValues;
+import android.transition.Visibility;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * EpicenterClipReveal captures the {@link View#getClipBounds()} before and
+ * after the scene change and animates between those and the epicenter bounds
+ * during a visibility transition.
+ */
+public class EpicenterClipReveal extends Visibility {
+ private static final String PROPNAME_CLIP = "android:epicenterReveal:clip";
+ private static final String PROPNAME_BOUNDS = "android:epicenterReveal:bounds";
+
+ public EpicenterClipReveal() {}
+
+ public EpicenterClipReveal(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void captureStartValues(TransitionValues transitionValues) {
+ super.captureStartValues(transitionValues);
+ captureValues(transitionValues);
+ }
+
+ @Override
+ public void captureEndValues(TransitionValues transitionValues) {
+ super.captureEndValues(transitionValues);
+ captureValues(transitionValues);
+ }
+
+ private void captureValues(TransitionValues values) {
+ final View view = values.view;
+ if (view.getVisibility() == View.GONE) {
+ return;
+ }
+
+ final Rect clip = view.getClipBounds();
+ values.values.put(PROPNAME_CLIP, clip);
+
+ if (clip == null) {
+ final Rect bounds = new Rect(0, 0, view.getWidth(), view.getHeight());
+ values.values.put(PROPNAME_BOUNDS, bounds);
+ }
+ }
+
+ @Override
+ public Animator onAppear(ViewGroup sceneRoot, View view,
+ TransitionValues startValues, TransitionValues endValues) {
+ if (endValues == null) {
+ return null;
+ }
+
+ final Rect start = getEpicenter();
+ final Rect end = getBestRect(endValues);
+
+ // Prepare the view.
+ view.setClipBounds(start);
+
+ return createRectAnimator(view, start, end);
+ }
+
+ @Override
+ public Animator onDisappear(ViewGroup sceneRoot, View view,
+ TransitionValues startValues, TransitionValues endValues) {
+ if (startValues == null) {
+ return null;
+ }
+
+ final Rect start = getBestRect(startValues);
+ final Rect end = getEpicenter();
+
+ // Prepare the view.
+ view.setClipBounds(start);
+
+ return createRectAnimator(view, start, end);
+ }
+
+ private Rect getBestRect(TransitionValues values) {
+ final Rect clipRect = (Rect) values.values.get(PROPNAME_CLIP);
+ if (clipRect == null) {
+ return (Rect) values.values.get(PROPNAME_BOUNDS);
+ }
+ return clipRect;
+ }
+
+ private Animator createRectAnimator(View view, Rect start, Rect end) {
+ final RectEvaluator evaluator = new RectEvaluator(new Rect());
+ return ObjectAnimator.ofObject(view, "clipBounds", evaluator, start, end);
+ }
+}
diff --git a/core/java/com/android/internal/util/UserIcons.java b/core/java/com/android/internal/util/UserIcons.java
index e1e9d5e..c69d14f 100644
--- a/core/java/com/android/internal/util/UserIcons.java
+++ b/core/java/com/android/internal/util/UserIcons.java
@@ -48,9 +48,12 @@ public class UserIcons {
if (icon == null) {
return null;
}
- Bitmap bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(), icon.getIntrinsicHeight(),
- Bitmap.Config.ARGB_8888);
- icon.draw(new Canvas(bitmap));
+ final int width = icon.getIntrinsicWidth();
+ final int height = icon.getIntrinsicHeight();
+ Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ icon.setBounds(0, 0, width, height);
+ icon.draw(canvas);
return bitmap;
}
diff --git a/core/java/com/android/internal/view/menu/ActionMenuItemView.java b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
index 7eec392..f75b139 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
@@ -217,14 +217,14 @@ public class ActionMenuItemView extends TextView
}
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
onPopulateAccessibilityEvent(event);
return true;
}
@Override
- public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
- super.onPopulateAccessibilityEvent(event);
+ public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onPopulateAccessibilityEventInternal(event);
final CharSequence cdesc = getContentDescription();
if (!TextUtils.isEmpty(cdesc)) {
event.getText().add(cdesc);
diff --git a/core/java/com/android/internal/view/menu/ListMenuItemView.java b/core/java/com/android/internal/view/menu/ListMenuItemView.java
index 692bdac..29ac3f3 100644
--- a/core/java/com/android/internal/view/menu/ListMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ListMenuItemView.java
@@ -276,8 +276,8 @@ public class ListMenuItemView extends LinearLayout implements MenuView.ItemView
}
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
if (mItemData != null && mItemData.hasSubMenu()) {
info.setCanOpenPopup(true);
diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
index 99bb1ac..2b20b38 100644
--- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
@@ -118,6 +118,10 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On
mDropDownGravity = gravity;
}
+ public int getGravity() {
+ return mDropDownGravity;
+ }
+
public void show() {
if (!tryShow()) {
throw new IllegalStateException("MenuPopupHelper cannot be used without an anchor");
@@ -135,7 +139,7 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On
mPopup.setAdapter(mAdapter);
mPopup.setModal(true);
- View anchor = mAnchorView;
+ final View anchor = mAnchorView;
if (anchor != null) {
final boolean addGlobalListener = mTreeObserver == null;
mTreeObserver = anchor.getViewTreeObserver(); // Refresh to latest
diff --git a/core/java/com/android/internal/widget/AccessibleDateAnimator.java b/core/java/com/android/internal/widget/AccessibleDateAnimator.java
index e91a55c..f97a5d1 100644
--- a/core/java/com/android/internal/widget/AccessibleDateAnimator.java
+++ b/core/java/com/android/internal/widget/AccessibleDateAnimator.java
@@ -40,7 +40,7 @@ public class AccessibleDateAnimator extends ViewAnimator {
* Announce the currently-selected date when launched.
*/
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
// Clear the event's current text so that only the current date will be spoken.
event.getText().clear();
@@ -51,6 +51,6 @@ public class AccessibleDateAnimator extends ViewAnimator {
event.getText().add(dateString);
return true;
}
- return super.dispatchPopulateAccessibilityEvent(event);
+ return super.dispatchPopulateAccessibilityEventInternal(event);
}
}
diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java
index 7c671e8..5d3f464 100644
--- a/core/java/com/android/internal/widget/ActionBarContextView.java
+++ b/core/java/com/android/internal/widget/ActionBarContextView.java
@@ -529,7 +529,7 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
// Action mode started
event.setSource(this);
@@ -537,7 +537,7 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi
event.setPackageName(getContext().getPackageName());
event.setContentDescription(mTitle);
} else {
- super.onInitializeAccessibilityEvent(event);
+ super.onInitializeAccessibilityEventInternal(event);
}
}
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index 654d08b..88436f8 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -1463,14 +1463,14 @@ public class ActionBarView extends AbsActionBarView implements DecorToolbar {
}
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
onPopulateAccessibilityEvent(event);
return true;
}
@Override
- public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
- super.onPopulateAccessibilityEvent(event);
+ public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onPopulateAccessibilityEventInternal(event);
final CharSequence cdesc = getContentDescription();
if (!TextUtils.isEmpty(cdesc)) {
event.getText().add(cdesc);
diff --git a/core/java/com/android/internal/widget/FaceUnlockView.java b/core/java/com/android/internal/widget/FaceUnlockView.java
deleted file mode 100644
index 121e601..0000000
--- a/core/java/com/android/internal/widget/FaceUnlockView.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2012 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 com.android.internal.widget;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.RelativeLayout;
-
-public class FaceUnlockView extends RelativeLayout {
- private static final String TAG = "FaceUnlockView";
-
- public FaceUnlockView(Context context) {
- this(context, null);
- }
-
- public FaceUnlockView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- private int resolveMeasured(int measureSpec, int desired)
- {
- int result = 0;
- int specSize = MeasureSpec.getSize(measureSpec);
- switch (MeasureSpec.getMode(measureSpec)) {
- case MeasureSpec.UNSPECIFIED:
- result = desired;
- break;
- case MeasureSpec.AT_MOST:
- result = Math.max(specSize, desired);
- break;
- case MeasureSpec.EXACTLY:
- default:
- result = specSize;
- }
- return result;
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- final int minimumWidth = getSuggestedMinimumWidth();
- final int minimumHeight = getSuggestedMinimumHeight();
- int viewWidth = resolveMeasured(widthMeasureSpec, minimumWidth);
- int viewHeight = resolveMeasured(heightMeasureSpec, minimumHeight);
-
- final int chosenSize = Math.min(viewWidth, viewHeight);
- final int newWidthMeasureSpec =
- MeasureSpec.makeMeasureSpec(chosenSize, MeasureSpec.AT_MOST);
- final int newHeightMeasureSpec =
- MeasureSpec.makeMeasureSpec(chosenSize, MeasureSpec.AT_MOST);
-
- super.onMeasure(newWidthMeasureSpec, newHeightMeasureSpec);
- }
-}
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index 9501f92..0cb1f38 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -31,5 +31,4 @@ interface ILockSettings {
boolean checkVoldPassword(int userId);
boolean havePattern(int userId);
boolean havePassword(int userId);
- void removeUser(int userId);
}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 0afc651..90821dc 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -18,14 +18,11 @@ package com.android.internal.widget;
import android.Manifest;
import android.app.ActivityManagerNative;
-import android.app.AlarmManager;
import android.app.admin.DevicePolicyManager;
import android.app.trust.TrustManager;
-import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
-import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.os.AsyncTask;
@@ -39,19 +36,12 @@ import android.os.UserManager;
import android.os.storage.IMountService;
import android.os.storage.StorageManager;
import android.provider.Settings;
-import android.telecom.TelecomManager;
import android.text.TextUtils;
import android.util.Log;
-import android.view.IWindowManager;
-import android.view.View;
-import android.widget.Button;
-import com.android.internal.R;
import com.google.android.collect.Lists;
-import java.io.ByteArrayOutputStream;
-import java.nio.charset.StandardCharsets;
-import libcore.util.HexEncoding;
+import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
@@ -59,6 +49,8 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import libcore.util.HexEncoding;
+
/**
* Utilities for the lock pattern and its settings.
*/
@@ -103,52 +95,36 @@ public class LockPatternUtils {
public static final int MIN_LOCK_PATTERN_SIZE = 4;
/**
+ * The minimum size of a valid password.
+ */
+ public static final int MIN_LOCK_PASSWORD_SIZE = 4;
+
+ /**
* The minimum number of dots the user must include in a wrong pattern
* attempt for it to be counted against the counts that affect
* {@link #FAILED_ATTEMPTS_BEFORE_TIMEOUT} and {@link #FAILED_ATTEMPTS_BEFORE_RESET}
*/
public static final int MIN_PATTERN_REGISTER_FAIL = MIN_LOCK_PATTERN_SIZE;
- /**
- * Tells the keyguard to show the user switcher when the keyguard is created.
- */
- public static final String KEYGUARD_SHOW_USER_SWITCHER = "showuserswitcher";
-
- /**
- * Tells the keyguard to show the security challenge when the keyguard is created.
- */
- public static final String KEYGUARD_SHOW_SECURITY_CHALLENGE = "showsecuritychallenge";
-
- /**
- * Tells the keyguard to show the widget with the specified id when the keyguard is created.
- */
- public static final String KEYGUARD_SHOW_APPWIDGET = "showappwidget";
-
- /**
- * The bit in LOCK_BIOMETRIC_WEAK_FLAGS to be used to indicate whether liveliness should
- * be used
- */
- public static final int FLAG_BIOMETRIC_WEAK_LIVELINESS = 0x1;
-
- /**
- * Pseudo-appwidget id we use to represent the default clock status widget
- */
- public static final int ID_DEFAULT_STATUS_WIDGET = -2;
-
+ @Deprecated
public final static String LOCKOUT_PERMANENT_KEY = "lockscreen.lockedoutpermanently";
public final static String LOCKOUT_ATTEMPT_DEADLINE = "lockscreen.lockoutattemptdeadline";
public final static String PATTERN_EVER_CHOSEN_KEY = "lockscreen.patterneverchosen";
public final static String PASSWORD_TYPE_KEY = "lockscreen.password_type";
- public static final String PASSWORD_TYPE_ALTERNATE_KEY = "lockscreen.password_type_alternate";
+ @Deprecated
+ public final static String PASSWORD_TYPE_ALTERNATE_KEY = "lockscreen.password_type_alternate";
public final static String LOCK_PASSWORD_SALT_KEY = "lockscreen.password_salt";
public final static String DISABLE_LOCKSCREEN_KEY = "lockscreen.disabled";
public final static String LOCKSCREEN_OPTIONS = "lockscreen.options";
+ @Deprecated
public final static String LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK
= "lockscreen.biometric_weak_fallback";
+ @Deprecated
public final static String BIOMETRIC_WEAK_EVER_CHOSEN_KEY
= "lockscreen.biometricweakeverchosen";
public final static String LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS
= "lockscreen.power_button_instantly_locks";
+ @Deprecated
public final static String LOCKSCREEN_WIDGETS_ENABLED = "lockscreen.widgets_enabled";
public final static String PASSWORD_HISTORY_KEY = "lockscreen.passwordhistory";
@@ -227,7 +203,11 @@ public class LockPatternUtils {
}
public int getRequestedPasswordHistoryLength() {
- return getDevicePolicyManager().getPasswordHistoryLength(null, getCurrentOrCallingUserId());
+ return getRequestedPasswordHistoryLength(getCurrentOrCallingUserId());
+ }
+
+ private int getRequestedPasswordHistoryLength(int userId) {
+ return getDevicePolicyManager().getPasswordHistoryLength(null, userId);
}
public int getRequestedPasswordMinimumLetters() {
@@ -289,14 +269,6 @@ public class LockPatternUtils {
}
}
- public void removeUser(int userId) {
- try {
- getLockSettings().removeUser(userId);
- } catch (RemoteException re) {
- Log.e(TAG, "Couldn't remove lock settings for user " + userId);
- }
- }
-
private int getCurrentOrCallingUserId() {
if (mMultiUserMode) {
// TODO: This is a little inefficient. See if all users of this are able to
@@ -359,9 +331,10 @@ public class LockPatternUtils {
* @return Whether the password matches any in the history.
*/
public boolean checkPasswordHistory(String password) {
+ int userId = getCurrentOrCallingUserId();
String passwordHashString = new String(
- passwordToHash(password, getCurrentOrCallingUserId()), StandardCharsets.UTF_8);
- String passwordHistory = getString(PASSWORD_HISTORY_KEY);
+ passwordToHash(password, userId), StandardCharsets.UTF_8);
+ String passwordHistory = getString(PASSWORD_HISTORY_KEY, userId);
if (passwordHistory == null) {
return false;
}
@@ -383,15 +356,7 @@ public class LockPatternUtils {
* Check to see if the user has stored a lock pattern.
* @return Whether a saved pattern exists.
*/
- public boolean savedPatternExists() {
- return savedPatternExists(getCurrentOrCallingUserId());
- }
-
- /**
- * Check to see if the user has stored a lock pattern.
- * @return Whether a saved pattern exists.
- */
- public boolean savedPatternExists(int userId) {
+ private boolean savedPatternExists(int userId) {
try {
return getLockSettings().havePattern(userId);
} catch (RemoteException re) {
@@ -403,15 +368,7 @@ public class LockPatternUtils {
* Check to see if the user has stored a lock pattern.
* @return Whether a saved pattern exists.
*/
- public boolean savedPasswordExists() {
- return savedPasswordExists(getCurrentOrCallingUserId());
- }
-
- /**
- * Check to see if the user has stored a lock pattern.
- * @return Whether a saved pattern exists.
- */
- public boolean savedPasswordExists(int userId) {
+ private boolean savedPasswordExists(int userId) {
try {
return getLockSettings().havePassword(userId);
} catch (RemoteException re) {
@@ -426,86 +383,62 @@ public class LockPatternUtils {
* @return True if the user has ever chosen a pattern.
*/
public boolean isPatternEverChosen() {
- return getBoolean(PATTERN_EVER_CHOSEN_KEY, false);
+ return getBoolean(PATTERN_EVER_CHOSEN_KEY, false, getCurrentOrCallingUserId());
}
/**
- * Return true if the user has ever chosen biometric weak. This is true even if biometric
- * weak is not current set.
- *
- * @return True if the user has ever chosen biometric weak.
+ * Used by device policy manager to validate the current password
+ * information it has.
*/
- public boolean isBiometricWeakEverChosen() {
- return getBoolean(BIOMETRIC_WEAK_EVER_CHOSEN_KEY, false);
+ public int getActivePasswordQuality() {
+ return getActivePasswordQuality(getCurrentOrCallingUserId());
}
/**
* Used by device policy manager to validate the current password
* information it has.
*/
- public int getActivePasswordQuality() {
- int activePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
- // Note we don't want to use getKeyguardStoredPasswordQuality() because we want this to
- // return biometric_weak if that is being used instead of the backup
- int quality =
- (int) getLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
- switch (quality) {
- case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
- if (isLockPatternEnabled()) {
- activePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
- }
- break;
- case DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK:
- if (isBiometricWeakInstalled()) {
- activePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK;
- }
- break;
- case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
- if (isLockPasswordEnabled()) {
- activePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
- }
- break;
- case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX:
- if (isLockPasswordEnabled()) {
- activePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
- }
- break;
- case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
- if (isLockPasswordEnabled()) {
- activePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
- }
- break;
- case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
- if (isLockPasswordEnabled()) {
- activePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
- }
- break;
- case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX:
- if (isLockPasswordEnabled()) {
- activePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
- }
- break;
+ public int getActivePasswordQuality(int userId) {
+ int quality = getKeyguardStoredPasswordQuality(userId);
+
+ if (isLockPasswordEnabled(quality, userId)) {
+ // Quality is a password and a password exists. Return the quality.
+ return quality;
}
- return activePasswordQuality;
+ if (isLockPatternEnabled(quality, userId)) {
+ // Quality is a pattern and a pattern exists. Return the quality.
+ return quality;
+ }
+
+ return DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
}
- public void clearLock(boolean isFallback) {
- clearLock(isFallback, getCurrentOrCallingUserId());
+ public void clearLock() {
+ clearLock(getCurrentOrCallingUserId());
}
/**
* Clear any lock pattern or password.
*/
- public void clearLock(boolean isFallback, int userHandle) {
- if(!isFallback) deleteGallery(userHandle);
- saveLockPassword(null, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, isFallback,
- userHandle);
- setLockPatternEnabled(false, userHandle);
- saveLockPattern(null, isFallback, userHandle);
+ public void clearLock(int userHandle) {
setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userHandle);
- setLong(PASSWORD_TYPE_ALTERNATE_KEY, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
- userHandle);
+
+ try {
+ getLockSettings().setLockPassword(null, userHandle);
+ getLockSettings().setLockPattern(null, userHandle);
+ } catch (RemoteException e) {
+ // well, we tried...
+ }
+
+ if (userHandle == UserHandle.USER_OWNER) {
+ // Set the encryption password to default.
+ updateEncryptionPassword(StorageManager.CRYPT_TYPE_DEFAULT, null);
+ }
+
+ getDevicePolicyManager().setActivePasswordState(
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, 0, 0, 0, 0, 0, userHandle);
+
onAfterChangingPassword(userHandle);
}
@@ -516,7 +449,7 @@ public class LockPatternUtils {
* @param disable Disables lock screen when true
*/
public void setLockScreenDisabled(boolean disable) {
- setLong(DISABLE_LOCKSCREEN_KEY, disable ? 1 : 0);
+ setBoolean(DISABLE_LOCKSCREEN_KEY, disable, getCurrentOrCallingUserId());
}
/**
@@ -526,7 +459,7 @@ public class LockPatternUtils {
* @return true if lock screen is can be disabled
*/
public boolean isLockScreenDisabled() {
- if (!isSecure() && getLong(DISABLE_LOCKSCREEN_KEY, 0) != 0) {
+ if (!isSecure() && getBoolean(DISABLE_LOCKSCREEN_KEY, false, getCurrentOrCallingUserId())) {
// Check if the number of switchable users forces the lockscreen.
final List<UserInfo> users = UserManager.get(mContext).getUsers(true);
final int userCount = users.size();
@@ -542,96 +475,57 @@ public class LockPatternUtils {
}
/**
- * Calls back SetupFaceLock to delete the temporary gallery file
- */
- public void deleteTempGallery() {
- Intent intent = new Intent().setAction("com.android.facelock.DELETE_GALLERY");
- intent.putExtra("deleteTempGallery", true);
- mContext.sendBroadcast(intent);
- }
-
- /**
- * Calls back SetupFaceLock to delete the gallery file when the lock type is changed
- */
- void deleteGallery(int userId) {
- if(usingBiometricWeak(userId)) {
- Intent intent = new Intent().setAction("com.android.facelock.DELETE_GALLERY");
- intent.putExtra("deleteGallery", true);
- mContext.sendBroadcastAsUser(intent, new UserHandle(userId));
- }
- }
-
- /**
* Save a lock pattern.
* @param pattern The new pattern to save.
*/
public void saveLockPattern(List<LockPatternView.Cell> pattern) {
- this.saveLockPattern(pattern, false);
- }
-
- /**
- * Save a lock pattern.
- * @param pattern The new pattern to save.
- */
- public void saveLockPattern(List<LockPatternView.Cell> pattern, boolean isFallback) {
- this.saveLockPattern(pattern, isFallback, getCurrentOrCallingUserId());
+ this.saveLockPattern(pattern, getCurrentOrCallingUserId());
}
/**
* Save a lock pattern.
* @param pattern The new pattern to save.
- * @param isFallback Specifies if this is a fallback to biometric weak
* @param userId the user whose pattern is to be saved.
*/
- public void saveLockPattern(List<LockPatternView.Cell> pattern, boolean isFallback,
- int userId) {
+ public void saveLockPattern(List<LockPatternView.Cell> pattern, int userId) {
try {
+ if (pattern == null || pattern.size() < MIN_LOCK_PATTERN_SIZE) {
+ throw new IllegalArgumentException("pattern must not be null and at least "
+ + MIN_LOCK_PATTERN_SIZE + " dots long.");
+ }
+
getLockSettings().setLockPattern(patternToString(pattern), userId);
DevicePolicyManager dpm = getDevicePolicyManager();
- if (pattern != null) {
- // Update the device encryption password.
- if (userId == UserHandle.USER_OWNER
- && LockPatternUtils.isDeviceEncryptionEnabled()) {
- final boolean required = isCredentialRequiredToDecrypt(true);
- if (!required) {
- clearEncryptionPassword();
- } else {
- String stringPattern = patternToString(pattern);
- updateEncryptionPassword(StorageManager.CRYPT_TYPE_PATTERN, stringPattern);
- }
- }
- setBoolean(PATTERN_EVER_CHOSEN_KEY, true, userId);
- if (!isFallback) {
- deleteGallery(userId);
- setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, userId);
- dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING,
- pattern.size(), 0, 0, 0, 0, 0, 0, userId);
+ // Update the device encryption password.
+ if (userId == UserHandle.USER_OWNER
+ && LockPatternUtils.isDeviceEncryptionEnabled()) {
+ final boolean required = isCredentialRequiredToDecrypt(true);
+ if (!required) {
+ clearEncryptionPassword();
} else {
- setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK, userId);
- setLong(PASSWORD_TYPE_ALTERNATE_KEY,
- DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, userId);
- finishBiometricWeak(userId);
- dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK,
- 0, 0, 0, 0, 0, 0, 0, userId);
+ String stringPattern = patternToString(pattern);
+ updateEncryptionPassword(StorageManager.CRYPT_TYPE_PATTERN, stringPattern);
}
- } else {
- dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0,
- 0, 0, 0, 0, 0, userId);
}
+
+ setBoolean(PATTERN_EVER_CHOSEN_KEY, true, userId);
+
+ setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, userId);
+ dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING,
+ pattern.size(), 0, 0, 0, 0, 0, 0, userId);
onAfterChangingPassword(userId);
} catch (RemoteException re) {
Log.e(TAG, "Couldn't save lock pattern " + re);
}
}
- private void updateCryptoUserInfo() {
- int userId = getCurrentOrCallingUserId();
+ private void updateCryptoUserInfo(int userId) {
if (userId != UserHandle.USER_OWNER) {
return;
}
- final String ownerInfo = isOwnerInfoEnabled() ? getOwnerInfo(userId) : "";
+ final String ownerInfo = isOwnerInfoEnabled(userId) ? getOwnerInfo(userId) : "";
IBinder service = ServiceManager.getService("mount");
if (service == null) {
@@ -650,20 +544,25 @@ public class LockPatternUtils {
public void setOwnerInfo(String info, int userId) {
setString(LOCK_SCREEN_OWNER_INFO, info, userId);
- updateCryptoUserInfo();
+ updateCryptoUserInfo(userId);
}
public void setOwnerInfoEnabled(boolean enabled) {
- setBoolean(LOCK_SCREEN_OWNER_INFO_ENABLED, enabled);
- updateCryptoUserInfo();
+ int userId = getCurrentOrCallingUserId();
+ setBoolean(LOCK_SCREEN_OWNER_INFO_ENABLED, enabled, userId);
+ updateCryptoUserInfo(userId);
}
public String getOwnerInfo(int userId) {
- return getString(LOCK_SCREEN_OWNER_INFO);
+ return getString(LOCK_SCREEN_OWNER_INFO, userId);
}
public boolean isOwnerInfoEnabled() {
- return getBoolean(LOCK_SCREEN_OWNER_INFO_ENABLED, false);
+ return isOwnerInfoEnabled(getCurrentOrCallingUserId());
+ }
+
+ private boolean isOwnerInfoEnabled(int userId) {
+ return getBoolean(LOCK_SCREEN_OWNER_INFO_ENABLED, false, userId);
}
/**
@@ -789,19 +688,7 @@ public class LockPatternUtils {
* @param quality {@see DevicePolicyManager#getPasswordQuality(android.content.ComponentName)}
*/
public void saveLockPassword(String password, int quality) {
- this.saveLockPassword(password, quality, false, getCurrentOrCallingUserId());
- }
-
- /**
- * Save a lock password. Does not ensure that the password is as good
- * as the requested mode, but will adjust the mode to be as good as the
- * pattern.
- * @param password The password to save
- * @param quality {@see DevicePolicyManager#getPasswordQuality(android.content.ComponentName)}
- * @param isFallback Specifies if this is a fallback to biometric weak
- */
- public void saveLockPassword(String password, int quality, boolean isFallback) {
- saveLockPassword(password, quality, isFallback, getCurrentOrCallingUserId());
+ saveLockPassword(password, quality, getCurrentOrCallingUserId());
}
/**
@@ -810,108 +697,88 @@ public class LockPatternUtils {
* pattern.
* @param password The password to save
* @param quality {@see DevicePolicyManager#getPasswordQuality(android.content.ComponentName)}
- * @param isFallback Specifies if this is a fallback to biometric weak
* @param userHandle The userId of the user to change the password for
*/
- public void saveLockPassword(String password, int quality, boolean isFallback, int userHandle) {
+ public void saveLockPassword(String password, int quality, int userHandle) {
try {
DevicePolicyManager dpm = getDevicePolicyManager();
- if (!TextUtils.isEmpty(password)) {
- getLockSettings().setLockPassword(password, userHandle);
- int computedQuality = computePasswordQuality(password);
-
- // Update the device encryption password.
- if (userHandle == UserHandle.USER_OWNER
- && LockPatternUtils.isDeviceEncryptionEnabled()) {
- if (!isCredentialRequiredToDecrypt(true)) {
- clearEncryptionPassword();
- } else {
- boolean numeric = computedQuality
- == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
- boolean numericComplex = computedQuality
- == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
- int type = numeric || numericComplex ? StorageManager.CRYPT_TYPE_PIN
- : StorageManager.CRYPT_TYPE_PASSWORD;
- updateEncryptionPassword(type, password);
- }
+ if (password == null || password.length() < MIN_LOCK_PASSWORD_SIZE) {
+ throw new IllegalArgumentException("password must not be null and at least "
+ + "of length " + MIN_LOCK_PASSWORD_SIZE);
+ }
+
+ getLockSettings().setLockPassword(password, userHandle);
+ int computedQuality = computePasswordQuality(password);
+
+ // Update the device encryption password.
+ if (userHandle == UserHandle.USER_OWNER
+ && LockPatternUtils.isDeviceEncryptionEnabled()) {
+ if (!isCredentialRequiredToDecrypt(true)) {
+ clearEncryptionPassword();
+ } else {
+ boolean numeric = computedQuality
+ == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+ boolean numericComplex = computedQuality
+ == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
+ int type = numeric || numericComplex ? StorageManager.CRYPT_TYPE_PIN
+ : StorageManager.CRYPT_TYPE_PASSWORD;
+ updateEncryptionPassword(type, password);
}
+ }
- if (!isFallback) {
- deleteGallery(userHandle);
- setLong(PASSWORD_TYPE_KEY, Math.max(quality, computedQuality), userHandle);
- if (computedQuality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
- int letters = 0;
- int uppercase = 0;
- int lowercase = 0;
- int numbers = 0;
- int symbols = 0;
- int nonletter = 0;
- for (int i = 0; i < password.length(); i++) {
- char c = password.charAt(i);
- if (c >= 'A' && c <= 'Z') {
- letters++;
- uppercase++;
- } else if (c >= 'a' && c <= 'z') {
- letters++;
- lowercase++;
- } else if (c >= '0' && c <= '9') {
- numbers++;
- nonletter++;
- } else {
- symbols++;
- nonletter++;
- }
- }
- dpm.setActivePasswordState(Math.max(quality, computedQuality),
- password.length(), letters, uppercase, lowercase,
- numbers, symbols, nonletter, userHandle);
+ setLong(PASSWORD_TYPE_KEY, Math.max(quality, computedQuality), userHandle);
+ if (computedQuality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
+ int letters = 0;
+ int uppercase = 0;
+ int lowercase = 0;
+ int numbers = 0;
+ int symbols = 0;
+ int nonletter = 0;
+ for (int i = 0; i < password.length(); i++) {
+ char c = password.charAt(i);
+ if (c >= 'A' && c <= 'Z') {
+ letters++;
+ uppercase++;
+ } else if (c >= 'a' && c <= 'z') {
+ letters++;
+ lowercase++;
+ } else if (c >= '0' && c <= '9') {
+ numbers++;
+ nonletter++;
} else {
- // The password is not anything.
- dpm.setActivePasswordState(
- DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
- 0, 0, 0, 0, 0, 0, 0, userHandle);
+ symbols++;
+ nonletter++;
}
- } else {
- // Case where it's a fallback for biometric weak
- setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK,
- userHandle);
- setLong(PASSWORD_TYPE_ALTERNATE_KEY, Math.max(quality, computedQuality),
- userHandle);
- finishBiometricWeak(userHandle);
- dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK,
- 0, 0, 0, 0, 0, 0, 0, userHandle);
}
- // Add the password to the password history. We assume all
- // password hashes have the same length for simplicity of implementation.
- String passwordHistory = getString(PASSWORD_HISTORY_KEY, userHandle);
- if (passwordHistory == null) {
- passwordHistory = "";
- }
- int passwordHistoryLength = getRequestedPasswordHistoryLength();
- if (passwordHistoryLength == 0) {
- passwordHistory = "";
- } else {
- byte[] hash = passwordToHash(password, userHandle);
- passwordHistory = new String(hash, StandardCharsets.UTF_8) + "," + passwordHistory;
- // Cut it to contain passwordHistoryLength hashes
- // and passwordHistoryLength -1 commas.
- passwordHistory = passwordHistory.substring(0, Math.min(hash.length
- * passwordHistoryLength + passwordHistoryLength - 1, passwordHistory
- .length()));
- }
- setString(PASSWORD_HISTORY_KEY, passwordHistory, userHandle);
+ dpm.setActivePasswordState(Math.max(quality, computedQuality),
+ password.length(), letters, uppercase, lowercase,
+ numbers, symbols, nonletter, userHandle);
} else {
- // Empty password
- getLockSettings().setLockPassword(null, userHandle);
- if (userHandle == UserHandle.USER_OWNER) {
- // Set the encryption password to default.
- updateEncryptionPassword(StorageManager.CRYPT_TYPE_DEFAULT, null);
- }
-
+ // The password is not anything.
dpm.setActivePasswordState(
- DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, 0, 0, 0, 0, 0,
- userHandle);
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
+ 0, 0, 0, 0, 0, 0, 0, userHandle);
}
+
+ // Add the password to the password history. We assume all
+ // password hashes have the same length for simplicity of implementation.
+ String passwordHistory = getString(PASSWORD_HISTORY_KEY, userHandle);
+ if (passwordHistory == null) {
+ passwordHistory = "";
+ }
+ int passwordHistoryLength = getRequestedPasswordHistoryLength(userHandle);
+ if (passwordHistoryLength == 0) {
+ passwordHistory = "";
+ } else {
+ byte[] hash = passwordToHash(password, userHandle);
+ passwordHistory = new String(hash, StandardCharsets.UTF_8) + "," + passwordHistory;
+ // Cut it to contain passwordHistoryLength hashes
+ // and passwordHistoryLength -1 commas.
+ passwordHistory = passwordHistory.substring(0, Math.min(hash.length
+ * passwordHistoryLength + passwordHistoryLength - 1, passwordHistory
+ .length()));
+ }
+ setString(PASSWORD_HISTORY_KEY, passwordHistory, userHandle);
onAfterChangingPassword(userHandle);
} catch (RemoteException re) {
// Cant do much
@@ -971,31 +838,8 @@ public class LockPatternUtils {
* @return stored password quality
*/
public int getKeyguardStoredPasswordQuality(int userHandle) {
- int quality = (int) getLong(PASSWORD_TYPE_KEY,
+ return (int) getLong(PASSWORD_TYPE_KEY,
DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userHandle);
- // If the user has chosen to use weak biometric sensor, then return the backup locking
- // method and treat biometric as a special case.
- if (quality == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK) {
- quality = (int) getLong(PASSWORD_TYPE_ALTERNATE_KEY,
- DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userHandle);
- }
- return quality;
- }
-
- /**
- * @return true if the lockscreen method is set to biometric weak
- */
- public boolean usingBiometricWeak() {
- return usingBiometricWeak(getCurrentOrCallingUserId());
- }
-
- /**
- * @return true if the lockscreen method is set to biometric weak
- */
- public boolean usingBiometricWeak(int userId) {
- int quality = (int) getLong(
- PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userId);
- return quality == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK;
}
/**
@@ -1004,6 +848,10 @@ public class LockPatternUtils {
* @return The pattern.
*/
public static List<LockPatternView.Cell> stringToPattern(String string) {
+ if (string == null) {
+ return null;
+ }
+
List<LockPatternView.Cell> result = Lists.newArrayList();
final byte[] bytes = string.getBytes();
@@ -1106,126 +954,73 @@ public class LockPatternUtils {
}
/**
- * @return Whether the lock password is enabled, or if it is set as a backup for biometric weak
+ * @return Whether the lock screen is secured.
*/
- public boolean isLockPasswordEnabled() {
- long mode = getLong(PASSWORD_TYPE_KEY, 0);
- long backupMode = getLong(PASSWORD_TYPE_ALTERNATE_KEY, 0);
- final boolean passwordEnabled = mode == DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC
- || mode == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
- || mode == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX
- || mode == DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
- || mode == DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
- final boolean backupEnabled = backupMode == DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC
- || backupMode == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
- || backupMode == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX
- || backupMode == DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
- || backupMode == DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
-
- return savedPasswordExists() && (passwordEnabled ||
- (usingBiometricWeak() && backupEnabled));
+ public boolean isSecure() {
+ return isSecure(getCurrentOrCallingUserId());
}
/**
- * @return Whether the lock pattern is enabled, or if it is set as a backup for biometric weak
+ * @param userId the user for which to report the value
+ * @return Whether the lock screen is secured.
*/
- public boolean isLockPatternEnabled() {
- return isLockPatternEnabled(getCurrentOrCallingUserId());
+ public boolean isSecure(int userId) {
+ int mode = getKeyguardStoredPasswordQuality(userId);
+ return isLockPatternEnabled(mode, userId) || isLockPasswordEnabled(mode, userId);
}
/**
- * @return Whether the lock pattern is enabled, or if it is set as a backup for biometric weak
+ * @return Whether the lock password is enabled
*/
- public boolean isLockPatternEnabled(int userId) {
- final boolean backupEnabled =
- getLong(PASSWORD_TYPE_ALTERNATE_KEY,
- DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userId)
- == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
-
- return getBoolean(Settings.Secure.LOCK_PATTERN_ENABLED, false, userId)
- && (getLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
- userId) == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
- || (usingBiometricWeak(userId) && backupEnabled));
+ public boolean isLockPasswordEnabled() {
+ return isLockPasswordEnabled(getCurrentOrCallingUserId());
}
- /**
- * @return Whether biometric weak lock is installed and that the front facing camera exists
- */
- public boolean isBiometricWeakInstalled() {
- // Check that it's installed
- PackageManager pm = mContext.getPackageManager();
- try {
- pm.getPackageInfo("com.android.facelock", PackageManager.GET_ACTIVITIES);
- } catch (PackageManager.NameNotFoundException e) {
- return false;
- }
-
- // Check that the camera is enabled
- if (!pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT)) {
- return false;
- }
- if (getDevicePolicyManager().getCameraDisabled(null, getCurrentOrCallingUserId())) {
- return false;
- }
-
- // TODO: If we decide not to proceed with Face Unlock as a trustlet, this must be changed
- // back to returning true. If we become certain that Face Unlock will be a trustlet, this
- // entire function and a lot of other code can be removed.
- if (DEBUG) Log.d(TAG, "Forcing isBiometricWeakInstalled() to return false to disable it");
- return false;
+ public boolean isLockPasswordEnabled(int userId) {
+ return isLockPasswordEnabled(getKeyguardStoredPasswordQuality(userId), userId);
}
- /**
- * Set whether biometric weak liveliness is enabled.
- */
- public void setBiometricWeakLivelinessEnabled(boolean enabled) {
- long currentFlag = getLong(Settings.Secure.LOCK_BIOMETRIC_WEAK_FLAGS, 0L);
- long newFlag;
- if (enabled) {
- newFlag = currentFlag | FLAG_BIOMETRIC_WEAK_LIVELINESS;
- } else {
- newFlag = currentFlag & ~FLAG_BIOMETRIC_WEAK_LIVELINESS;
- }
- setLong(Settings.Secure.LOCK_BIOMETRIC_WEAK_FLAGS, newFlag);
+ private boolean isLockPasswordEnabled(int mode, int userId) {
+ final boolean passwordEnabled = mode == DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC
+ || mode == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
+ || mode == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX
+ || mode == DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
+ || mode == DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+ return passwordEnabled && savedPasswordExists(userId);
}
/**
- * @return Whether the biometric weak liveliness is enabled.
+ * @return Whether the lock pattern is enabled
*/
- public boolean isBiometricWeakLivelinessEnabled() {
- long currentFlag = getLong(Settings.Secure.LOCK_BIOMETRIC_WEAK_FLAGS, 0L);
- return ((currentFlag & FLAG_BIOMETRIC_WEAK_LIVELINESS) != 0);
+ public boolean isLockPatternEnabled() {
+ return isLockPatternEnabled(getCurrentOrCallingUserId());
}
- /**
- * Set whether the lock pattern is enabled.
- */
- public void setLockPatternEnabled(boolean enabled) {
- setLockPatternEnabled(enabled, getCurrentOrCallingUserId());
+ public boolean isLockPatternEnabled(int userId) {
+ return isLockPatternEnabled(getKeyguardStoredPasswordQuality(userId), userId);
}
- /**
- * Set whether the lock pattern is enabled.
- */
- public void setLockPatternEnabled(boolean enabled, int userHandle) {
- setBoolean(Settings.Secure.LOCK_PATTERN_ENABLED, enabled, userHandle);
+ private boolean isLockPatternEnabled(int mode, int userId) {
+ return mode == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
+ && savedPatternExists(userId);
}
/**
* @return Whether the visible pattern is enabled.
*/
public boolean isVisiblePatternEnabled() {
- return getBoolean(Settings.Secure.LOCK_PATTERN_VISIBLE, false);
+ return getBoolean(Settings.Secure.LOCK_PATTERN_VISIBLE, false, getCurrentOrCallingUserId());
}
/**
* Set whether the visible pattern is enabled.
*/
public void setVisiblePatternEnabled(boolean enabled) {
- setBoolean(Settings.Secure.LOCK_PATTERN_VISIBLE, enabled);
+ int userId = getCurrentOrCallingUserId();
+
+ setBoolean(Settings.Secure.LOCK_PATTERN_VISIBLE, enabled, userId);
// Update for crypto if owner
- int userId = getCurrentOrCallingUserId();
if (userId != UserHandle.USER_OWNER) {
return;
}
@@ -1259,7 +1054,7 @@ public class LockPatternUtils {
*/
public long setLockoutAttemptDeadline() {
final long deadline = SystemClock.elapsedRealtime() + FAILED_ATTEMPT_TIMEOUT_MS;
- setLong(LOCKOUT_ATTEMPT_DEADLINE, deadline);
+ setLong(LOCKOUT_ATTEMPT_DEADLINE, deadline, getCurrentOrCallingUserId());
return deadline;
}
@@ -1269,7 +1064,7 @@ public class LockPatternUtils {
* enter a pattern.
*/
public long getLockoutAttemptDeadline() {
- final long deadline = getLong(LOCKOUT_ATTEMPT_DEADLINE, 0L);
+ final long deadline = getLong(LOCKOUT_ATTEMPT_DEADLINE, 0L, getCurrentOrCallingUserId());
final long now = SystemClock.elapsedRealtime();
if (deadline < now || deadline > (now + FAILED_ATTEMPT_TIMEOUT_MS)) {
return 0L;
@@ -1277,51 +1072,6 @@ public class LockPatternUtils {
return deadline;
}
- /**
- * @return Whether the user is permanently locked out until they verify their
- * credentials. Occurs after {@link #FAILED_ATTEMPTS_BEFORE_RESET} failed
- * attempts.
- */
- public boolean isPermanentlyLocked() {
- return getBoolean(LOCKOUT_PERMANENT_KEY, false);
- }
-
- /**
- * Set the state of whether the device is permanently locked, meaning the user
- * must authenticate via other means.
- *
- * @param locked Whether the user is permanently locked out until they verify their
- * credentials. Occurs after {@link #FAILED_ATTEMPTS_BEFORE_RESET} failed
- * attempts.
- */
- public void setPermanentlyLocked(boolean locked) {
- setBoolean(LOCKOUT_PERMANENT_KEY, locked);
- }
-
- public boolean isEmergencyCallCapable() {
- return mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_voice_capable);
- }
-
- public boolean isPukUnlockScreenEnable() {
- return mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_enable_puk_unlock_screen);
- }
-
- public boolean isEmergencyCallEnabledWhileSimLocked() {
- return mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_enable_emergency_call_while_sim_locked);
- }
-
- /**
- * @return A formatted string of the next alarm (for showing on the lock screen),
- * or null if there is no next alarm.
- */
- public AlarmManager.AlarmClockInfo getNextAlarm() {
- AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
- return alarmManager.getNextAlarmClock(UserHandle.USER_CURRENT);
- }
-
private boolean getBoolean(String secureSettingKey, boolean defaultValue, int userId) {
try {
return getLockSettings().getBoolean(secureSettingKey, defaultValue, userId);
@@ -1330,10 +1080,6 @@ public class LockPatternUtils {
}
}
- private boolean getBoolean(String secureSettingKey, boolean defaultValue) {
- return getBoolean(secureSettingKey, defaultValue, getCurrentOrCallingUserId());
- }
-
private void setBoolean(String secureSettingKey, boolean enabled, int userId) {
try {
getLockSettings().setBoolean(secureSettingKey, enabled, userId);
@@ -1343,144 +1089,6 @@ public class LockPatternUtils {
}
}
- private void setBoolean(String secureSettingKey, boolean enabled) {
- setBoolean(secureSettingKey, enabled, getCurrentOrCallingUserId());
- }
-
- public int[] getAppWidgets() {
- return getAppWidgets(UserHandle.USER_CURRENT);
- }
-
- private int[] getAppWidgets(int userId) {
- String appWidgetIdString = Settings.Secure.getStringForUser(
- mContentResolver, Settings.Secure.LOCK_SCREEN_APPWIDGET_IDS, userId);
- String delims = ",";
- if (appWidgetIdString != null && appWidgetIdString.length() > 0) {
- String[] appWidgetStringIds = appWidgetIdString.split(delims);
- int[] appWidgetIds = new int[appWidgetStringIds.length];
- for (int i = 0; i < appWidgetStringIds.length; i++) {
- String appWidget = appWidgetStringIds[i];
- try {
- appWidgetIds[i] = Integer.decode(appWidget);
- } catch (NumberFormatException e) {
- Log.d(TAG, "Error when parsing widget id " + appWidget);
- return null;
- }
- }
- return appWidgetIds;
- }
- return new int[0];
- }
-
- private static String combineStrings(int[] list, String separator) {
- int listLength = list.length;
-
- switch (listLength) {
- case 0: {
- return "";
- }
- case 1: {
- return Integer.toString(list[0]);
- }
- }
-
- int strLength = 0;
- int separatorLength = separator.length();
-
- String[] stringList = new String[list.length];
- for (int i = 0; i < listLength; i++) {
- stringList[i] = Integer.toString(list[i]);
- strLength += stringList[i].length();
- if (i < listLength - 1) {
- strLength += separatorLength;
- }
- }
-
- StringBuilder sb = new StringBuilder(strLength);
-
- for (int i = 0; i < listLength; i++) {
- sb.append(list[i]);
- if (i < listLength - 1) {
- sb.append(separator);
- }
- }
-
- return sb.toString();
- }
-
- // appwidget used when appwidgets are disabled (we make an exception for
- // default clock widget)
- public void writeFallbackAppWidgetId(int appWidgetId) {
- Settings.Secure.putIntForUser(mContentResolver,
- Settings.Secure.LOCK_SCREEN_FALLBACK_APPWIDGET_ID,
- appWidgetId,
- UserHandle.USER_CURRENT);
- }
-
- // appwidget used when appwidgets are disabled (we make an exception for
- // default clock widget)
- public int getFallbackAppWidgetId() {
- return Settings.Secure.getIntForUser(
- mContentResolver,
- Settings.Secure.LOCK_SCREEN_FALLBACK_APPWIDGET_ID,
- AppWidgetManager.INVALID_APPWIDGET_ID,
- UserHandle.USER_CURRENT);
- }
-
- private void writeAppWidgets(int[] appWidgetIds) {
- Settings.Secure.putStringForUser(mContentResolver,
- Settings.Secure.LOCK_SCREEN_APPWIDGET_IDS,
- combineStrings(appWidgetIds, ","),
- UserHandle.USER_CURRENT);
- }
-
- // TODO: log an error if this returns false
- public boolean addAppWidget(int widgetId, int index) {
- int[] widgets = getAppWidgets();
- if (widgets == null) {
- return false;
- }
- if (index < 0 || index > widgets.length) {
- return false;
- }
- int[] newWidgets = new int[widgets.length + 1];
- for (int i = 0, j = 0; i < newWidgets.length; i++) {
- if (index == i) {
- newWidgets[i] = widgetId;
- i++;
- }
- if (i < newWidgets.length) {
- newWidgets[i] = widgets[j];
- j++;
- }
- }
- writeAppWidgets(newWidgets);
- return true;
- }
-
- public boolean removeAppWidget(int widgetId) {
- int[] widgets = getAppWidgets();
-
- if (widgets.length == 0) {
- return false;
- }
-
- int[] newWidgets = new int[widgets.length - 1];
- for (int i = 0, j = 0; i < widgets.length; i++) {
- if (widgets[i] == widgetId) {
- // continue...
- } else if (j >= newWidgets.length) {
- // we couldn't find the widget
- return false;
- } else {
- newWidgets[j] = widgets[i];
- j++;
- }
- }
- writeAppWidgets(newWidgets);
- return true;
- }
-
private long getLong(String secureSettingKey, long defaultValue, int userHandle) {
try {
return getLockSettings().getLong(secureSettingKey, defaultValue, userHandle);
@@ -1489,19 +1097,6 @@ public class LockPatternUtils {
}
}
- private long getLong(String secureSettingKey, long defaultValue) {
- try {
- return getLockSettings().getLong(secureSettingKey, defaultValue,
- getCurrentOrCallingUserId());
- } catch (RemoteException re) {
- return defaultValue;
- }
- }
-
- private void setLong(String secureSettingKey, long value) {
- setLong(secureSettingKey, value, getCurrentOrCallingUserId());
- }
-
private void setLong(String secureSettingKey, long value, int userHandle) {
try {
getLockSettings().setLong(secureSettingKey, value, userHandle);
@@ -1511,10 +1106,6 @@ public class LockPatternUtils {
}
}
- private String getString(String secureSettingKey) {
- return getString(secureSettingKey, getCurrentOrCallingUserId());
- }
-
private String getString(String secureSettingKey, int userHandle) {
try {
return getLockSettings().getString(secureSettingKey, null, userHandle);
@@ -1532,134 +1123,13 @@ public class LockPatternUtils {
}
}
- public boolean isSecure() {
- return isSecure(getCurrentOrCallingUserId());
- }
-
- public boolean isSecure(int userId) {
- long mode = getKeyguardStoredPasswordQuality(userId);
- final boolean isPattern = mode == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
- final boolean isPassword = mode == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
- || mode == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX
- || mode == DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC
- || mode == DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
- || mode == DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
- final boolean secure =
- isPattern && isLockPatternEnabled(userId) && savedPatternExists(userId)
- || isPassword && savedPasswordExists(userId);
- return secure;
- }
-
- /**
- * Sets the emergency button visibility based on isEmergencyCallCapable().
- *
- * If the emergency button is visible, sets the text on the emergency button
- * to indicate what action will be taken.
- *
- * If there's currently a call in progress, the button will take them to the call
- * @param button The button to update
- * @param shown Indicates whether the given screen wants the emergency button to show at all
- * @param showIcon Indicates whether to show a phone icon for the button.
- */
- public void updateEmergencyCallButtonState(Button button, boolean shown, boolean showIcon) {
- if (isEmergencyCallCapable() && shown) {
- button.setVisibility(View.VISIBLE);
- } else {
- button.setVisibility(View.GONE);
- return;
- }
-
- int textId;
- if (isInCall()) {
- // show "return to call" text and show phone icon
- textId = R.string.lockscreen_return_to_call;
- int phoneCallIcon = showIcon ? R.drawable.stat_sys_phone_call : 0;
- button.setCompoundDrawablesWithIntrinsicBounds(phoneCallIcon, 0, 0, 0);
- } else {
- textId = R.string.lockscreen_emergency_call;
- int emergencyIcon = showIcon ? R.drawable.ic_emergency : 0;
- button.setCompoundDrawablesWithIntrinsicBounds(emergencyIcon, 0, 0, 0);
- }
- button.setText(textId);
- }
-
- /**
- * Resumes a call in progress. Typically launched from the EmergencyCall button
- * on various lockscreens.
- */
- public void resumeCall() {
- getTelecommManager().showInCallScreen(false);
- }
-
- /**
- * @return {@code true} if there is a call currently in progress, {@code false} otherwise.
- */
- public boolean isInCall() {
- return getTelecommManager().isInCall();
- }
-
- private TelecomManager getTelecommManager() {
- return (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
- }
-
- private void finishBiometricWeak(int userId) {
- setBoolean(BIOMETRIC_WEAK_EVER_CHOSEN_KEY, true, userId);
-
- // Launch intent to show final screen, this also
- // moves the temporary gallery to the actual gallery
- Intent intent = new Intent();
- intent.setClassName("com.android.facelock",
- "com.android.facelock.SetupEndScreen");
- mContext.startActivityAsUser(intent, new UserHandle(userId));
- }
-
public void setPowerButtonInstantlyLocks(boolean enabled) {
- setBoolean(LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS, enabled);
+ setBoolean(LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS, enabled, getCurrentOrCallingUserId());
}
public boolean getPowerButtonInstantlyLocks() {
- return getBoolean(LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS, true);
- }
-
- public static boolean isSafeModeEnabled() {
- try {
- return IWindowManager.Stub.asInterface(
- ServiceManager.getService("window")).isSafeModeEnabled();
- } catch (RemoteException e) {
- // Shouldn't happen!
- }
- return false;
- }
-
- /**
- * Determine whether the user has selected any non-system widgets in keyguard
- *
- * @return true if widgets have been selected
- */
- public boolean hasWidgetsEnabledInKeyguard(int userid) {
- int widgets[] = getAppWidgets(userid);
- for (int i = 0; i < widgets.length; i++) {
- if (widgets[i] > 0) {
- return true;
- }
- }
- return false;
- }
-
- public boolean getWidgetsEnabled() {
- return getWidgetsEnabled(getCurrentOrCallingUserId());
- }
-
- public boolean getWidgetsEnabled(int userId) {
- return getBoolean(LOCKSCREEN_WIDGETS_ENABLED, false, userId);
- }
-
- public void setWidgetsEnabled(boolean enabled) {
- setWidgetsEnabled(enabled, getCurrentOrCallingUserId());
- }
-
- public void setWidgetsEnabled(boolean enabled, int userId) {
- setBoolean(LOCKSCREEN_WIDGETS_ENABLED, enabled, userId);
+ return getBoolean(LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS, true,
+ getCurrentOrCallingUserId());
}
public void setEnabledTrustAgents(Collection<ComponentName> activeTrustAgents) {
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
index 9fa6882..52bbabf 100644
--- a/core/java/com/android/internal/widget/LockPatternView.java
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -65,8 +65,8 @@ public class LockPatternView extends View {
private boolean mDrawingProfilingStarted = false;
- private Paint mPaint = new Paint();
- private Paint mPathPaint = new Paint();
+ private final Paint mPaint = new Paint();
+ private final Paint mPathPaint = new Paint();
/**
* How many milliseconds we spend animating each circle of a lock pattern
@@ -82,7 +82,7 @@ public class LockPatternView extends View {
private static final float DRAG_THRESHHOLD = 0.0f;
private OnPatternListener mOnPatternListener;
- private ArrayList<Cell> mPattern = new ArrayList<Cell>(9);
+ private final ArrayList<Cell> mPattern = new ArrayList<Cell>(9);
/**
* Lookup table for the circles of the pattern we are currently drawing.
@@ -90,7 +90,7 @@ public class LockPatternView extends View {
* in which case we use this to hold the cells we are drawing for the in
* progress animation.
*/
- private boolean[][] mPatternDrawLookup = new boolean[3][3];
+ private final boolean[][] mPatternDrawLookup = new boolean[3][3];
/**
* the in progress point:
@@ -122,24 +122,27 @@ public class LockPatternView extends View {
private int mErrorColor;
private int mSuccessColor;
- private Interpolator mFastOutSlowInInterpolator;
- private Interpolator mLinearOutSlowInInterpolator;
+ private final Interpolator mFastOutSlowInInterpolator;
+ private final Interpolator mLinearOutSlowInInterpolator;
/**
* Represents a cell in the 3 X 3 matrix of the unlock pattern view.
*/
- public static class Cell {
- int row;
- int column;
+ public static final class Cell {
+ final int row;
+ final int column;
// keep # objects limited to 9
- static Cell[][] sCells = new Cell[3][3];
- static {
+ private static final Cell[][] sCells = createCells();
+
+ private static Cell[][] createCells() {
+ Cell[][] res = new Cell[3][3];
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
- sCells[i][j] = new Cell(i, j);
+ res[i][j] = new Cell(i, j);
}
}
+ return res;
}
/**
@@ -160,11 +163,7 @@ public class LockPatternView extends View {
return column;
}
- /**
- * @param row The row of the cell.
- * @param column The column of the cell.
- */
- public static synchronized Cell of(int row, int column) {
+ public static Cell of(int row, int column) {
checkRange(row, column);
return sCells[row][column];
}
@@ -178,6 +177,7 @@ public class LockPatternView extends View {
}
}
+ @Override
public String toString() {
return "(row=" + row + ",clmn=" + column + ")";
}
@@ -722,7 +722,7 @@ public class LockPatternView extends View {
handleActionDown(event);
return true;
case MotionEvent.ACTION_UP:
- handleActionUp(event);
+ handleActionUp();
return true;
case MotionEvent.ACTION_MOVE:
handleActionMove(event);
@@ -812,7 +812,7 @@ public class LockPatternView extends View {
announceForAccessibility(mContext.getString(resId));
}
- private void handleActionUp(MotionEvent event) {
+ private void handleActionUp() {
// report pattern detected
if (!mPattern.isEmpty()) {
mPatternInProgress = false;
@@ -1119,12 +1119,15 @@ public class LockPatternView extends View {
dest.writeValue(mTactileFeedbackEnabled);
}
+ @SuppressWarnings({ "unused", "hiding" }) // Found using reflection
public static final Parcelable.Creator<SavedState> CREATOR =
new Creator<SavedState>() {
+ @Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
+ @Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
diff --git a/core/java/com/android/internal/widget/ScrollingTabContainerView.java b/core/java/com/android/internal/widget/ScrollingTabContainerView.java
index d6bd1d6..a306697 100644
--- a/core/java/com/android/internal/widget/ScrollingTabContainerView.java
+++ b/core/java/com/android/internal/widget/ScrollingTabContainerView.java
@@ -150,7 +150,9 @@ public class ScrollingTabContainerView extends HorizontalScrollView
addView(mTabSpinner, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.MATCH_PARENT));
if (mTabSpinner.getAdapter() == null) {
- mTabSpinner.setAdapter(new TabAdapter());
+ final TabAdapter adapter = new TabAdapter(mContext);
+ adapter.setDropDownViewContext(mTabSpinner.getPopupContext());
+ mTabSpinner.setAdapter(adapter);
}
if (mTabSelector != null) {
removeCallbacks(mTabSelector);
@@ -276,8 +278,8 @@ public class ScrollingTabContainerView extends HorizontalScrollView
}
}
- private TabView createTabView(ActionBar.Tab tab, boolean forAdapter) {
- final TabView tabView = new TabView(getContext(), tab, forAdapter);
+ private TabView createTabView(Context context, ActionBar.Tab tab, boolean forAdapter) {
+ final TabView tabView = new TabView(context, tab, forAdapter);
if (forAdapter) {
tabView.setBackgroundDrawable(null);
tabView.setLayoutParams(new ListView.LayoutParams(ListView.LayoutParams.MATCH_PARENT,
@@ -294,7 +296,7 @@ public class ScrollingTabContainerView extends HorizontalScrollView
}
public void addTab(ActionBar.Tab tab, boolean setSelected) {
- TabView tabView = createTabView(tab, false);
+ TabView tabView = createTabView(mContext, tab, false);
mTabLayout.addView(tabView, new LinearLayout.LayoutParams(0,
LayoutParams.MATCH_PARENT, 1));
if (mTabSpinner != null) {
@@ -309,7 +311,7 @@ public class ScrollingTabContainerView extends HorizontalScrollView
}
public void addTab(ActionBar.Tab tab, int position, boolean setSelected) {
- final TabView tabView = createTabView(tab, false);
+ final TabView tabView = createTabView(mContext, tab, false);
mTabLayout.addView(tabView, position, new LinearLayout.LayoutParams(
0, LayoutParams.MATCH_PARENT, 1));
if (mTabSpinner != null) {
@@ -391,15 +393,15 @@ public class ScrollingTabContainerView extends HorizontalScrollView
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
// This view masquerades as an action bar tab.
event.setClassName(ActionBar.Tab.class.getName());
}
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
// This view masquerades as an action bar tab.
info.setClassName(ActionBar.Tab.class.getName());
}
@@ -514,6 +516,16 @@ public class ScrollingTabContainerView extends HorizontalScrollView
}
private class TabAdapter extends BaseAdapter {
+ private Context mDropDownContext;
+
+ public TabAdapter(Context context) {
+ setDropDownViewContext(context);
+ }
+
+ public void setDropDownViewContext(Context context) {
+ mDropDownContext = context;
+ }
+
@Override
public int getCount() {
return mTabLayout.getChildCount();
@@ -532,7 +544,18 @@ public class ScrollingTabContainerView extends HorizontalScrollView
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
- convertView = createTabView((ActionBar.Tab) getItem(position), true);
+ convertView = createTabView(mContext, (ActionBar.Tab) getItem(position), true);
+ } else {
+ ((TabView) convertView).bindTab((ActionBar.Tab) getItem(position));
+ }
+ return convertView;
+ }
+
+ @Override
+ public View getDropDownView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = createTabView(mDropDownContext,
+ (ActionBar.Tab) getItem(position), true);
} else {
((TabView) convertView).bindTab((ActionBar.Tab) getItem(position));
}
diff --git a/core/java/com/android/internal/widget/SizeAdaptiveLayout.java b/core/java/com/android/internal/widget/SizeAdaptiveLayout.java
deleted file mode 100644
index 5f3c5f9..0000000
--- a/core/java/com/android/internal/widget/SizeAdaptiveLayout.java
+++ /dev/null
@@ -1,442 +0,0 @@
-/*
- * Copyright (C) 2012 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 com.android.internal.widget;
-
-import java.lang.Math;
-
-import com.android.internal.R;
-
-import android.animation.Animator;
-import android.animation.Animator.AnimatorListener;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.StateListDrawable;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.StateSet;
-import android.view.View;
-import android.view.ViewDebug;
-import android.view.ViewGroup;
-import android.widget.RemoteViews.RemoteView;
-
-/**
- * A layout that switches between its children based on the requested layout height.
- * Each child specifies its minimum and maximum valid height. Results are undefined
- * if children specify overlapping ranges. A child may specify the maximum height
- * as 'unbounded' to indicate that it is willing to be displayed arbitrarily tall.
- *
- * <p>
- * See {@link SizeAdaptiveLayout.LayoutParams} for a full description of the
- * layout parameters used by SizeAdaptiveLayout.
- */
-@RemoteView
-public class SizeAdaptiveLayout extends ViewGroup {
-
- private static final String TAG = "SizeAdaptiveLayout";
- private static final boolean DEBUG = false;
- private static final boolean REPORT_BAD_BOUNDS = true;
- private static final long CROSSFADE_TIME = 250;
-
- // TypedArray indices
- private static final int MIN_VALID_HEIGHT =
- R.styleable.SizeAdaptiveLayout_Layout_layout_minHeight;
- private static final int MAX_VALID_HEIGHT =
- R.styleable.SizeAdaptiveLayout_Layout_layout_maxHeight;
-
- // view state
- private View mActiveChild;
- private View mLastActive;
-
- // animation state
- private AnimatorSet mTransitionAnimation;
- private AnimatorListener mAnimatorListener;
- private ObjectAnimator mFadePanel;
- private ObjectAnimator mFadeView;
- private int mCanceledAnimationCount;
- private View mEnteringView;
- private View mLeavingView;
- // View used to hide larger views under smaller ones to create a uniform crossfade
- private View mModestyPanel;
- private int mModestyPanelTop;
-
- public SizeAdaptiveLayout(Context context) {
- this(context, null);
- }
-
- public SizeAdaptiveLayout(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public SizeAdaptiveLayout(Context context, AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- public SizeAdaptiveLayout(
- Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- initialize();
- }
-
- private void initialize() {
- mModestyPanel = new View(getContext());
- // If the SizeAdaptiveLayout has a solid background, use it as a transition hint.
- Drawable background = getBackground();
- if (background instanceof StateListDrawable) {
- StateListDrawable sld = (StateListDrawable) background;
- sld.setState(StateSet.WILD_CARD);
- background = sld.getCurrent();
- }
- if (background instanceof ColorDrawable) {
- mModestyPanel.setBackgroundDrawable(background);
- }
- SizeAdaptiveLayout.LayoutParams layout =
- new SizeAdaptiveLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT);
- mModestyPanel.setLayoutParams(layout);
- addView(mModestyPanel);
- mFadePanel = ObjectAnimator.ofFloat(mModestyPanel, "alpha", 0f);
- mFadeView = ObjectAnimator.ofFloat(null, "alpha", 0f);
- mAnimatorListener = new BringToFrontOnEnd();
- mTransitionAnimation = new AnimatorSet();
- mTransitionAnimation.play(mFadeView).with(mFadePanel);
- mTransitionAnimation.setDuration(CROSSFADE_TIME);
- mTransitionAnimation.addListener(mAnimatorListener);
- }
-
- /**
- * Visible for testing
- * @hide
- */
- public Animator getTransitionAnimation() {
- return mTransitionAnimation;
- }
-
- /**
- * Visible for testing
- * @hide
- */
- public View getModestyPanel() {
- return mModestyPanel;
- }
-
- @Override
- public void onAttachedToWindow() {
- mLastActive = null;
- // make sure all views start off invisible.
- for (int i = 0; i < getChildCount(); i++) {
- getChildAt(i).setVisibility(View.GONE);
- }
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- if (DEBUG) Log.d(TAG, this + " measure spec: " +
- MeasureSpec.toString(heightMeasureSpec));
- View model = selectActiveChild(heightMeasureSpec);
- if (model == null) {
- setMeasuredDimension(0, 0);
- return;
- }
- SizeAdaptiveLayout.LayoutParams lp =
- (SizeAdaptiveLayout.LayoutParams) model.getLayoutParams();
- if (DEBUG) Log.d(TAG, "active min: " + lp.minHeight + " max: " + lp.maxHeight);
- measureChild(model, widthMeasureSpec, heightMeasureSpec);
- int childHeight = model.getMeasuredHeight();
- int childWidth = model.getMeasuredHeight();
- int childState = combineMeasuredStates(0, model.getMeasuredState());
- if (DEBUG) Log.d(TAG, "measured child at: " + childHeight);
- int resolvedWidth = resolveSizeAndState(childWidth, widthMeasureSpec, childState);
- int resolvedHeight = resolveSizeAndState(childHeight, heightMeasureSpec, childState);
- if (DEBUG) Log.d(TAG, "resolved to: " + resolvedHeight);
- int boundedHeight = clampSizeToBounds(resolvedHeight, model);
- if (DEBUG) Log.d(TAG, "bounded to: " + boundedHeight);
- setMeasuredDimension(resolvedWidth, boundedHeight);
- }
-
- private int clampSizeToBounds(int measuredHeight, View child) {
- SizeAdaptiveLayout.LayoutParams lp =
- (SizeAdaptiveLayout.LayoutParams) child.getLayoutParams();
- int heightIn = View.MEASURED_SIZE_MASK & measuredHeight;
- int height = Math.max(heightIn, lp.minHeight);
- if (lp.maxHeight != SizeAdaptiveLayout.LayoutParams.UNBOUNDED) {
- height = Math.min(height, lp.maxHeight);
- }
-
- if (REPORT_BAD_BOUNDS && heightIn != height) {
- Log.d(TAG, this + "child view " + child + " " +
- "measured out of bounds at " + heightIn +"px " +
- "clamped to " + height + "px");
- }
-
- return height;
- }
-
- //TODO extend to width and height
- private View selectActiveChild(int heightMeasureSpec) {
- final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
- final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
-
- View unboundedView = null;
- View tallestView = null;
- int tallestViewSize = 0;
- View smallestView = null;
- int smallestViewSize = Integer.MAX_VALUE;
- for (int i = 0; i < getChildCount(); i++) {
- View child = getChildAt(i);
- if (child != mModestyPanel) {
- SizeAdaptiveLayout.LayoutParams lp =
- (SizeAdaptiveLayout.LayoutParams) child.getLayoutParams();
- if (DEBUG) Log.d(TAG, "looking at " + i +
- " with min: " + lp.minHeight +
- " max: " + lp.maxHeight);
- if (lp.maxHeight == SizeAdaptiveLayout.LayoutParams.UNBOUNDED &&
- unboundedView == null) {
- unboundedView = child;
- }
- if (lp.maxHeight > tallestViewSize) {
- tallestViewSize = lp.maxHeight;
- tallestView = child;
- }
- if (lp.minHeight < smallestViewSize) {
- smallestViewSize = lp.minHeight;
- smallestView = child;
- }
- if (heightMode != MeasureSpec.UNSPECIFIED &&
- heightSize >= lp.minHeight && heightSize <= lp.maxHeight) {
- if (DEBUG) Log.d(TAG, " found exact match, finishing early");
- return child;
- }
- }
- }
- if (unboundedView != null) {
- tallestView = unboundedView;
- }
- if (heightMode == MeasureSpec.UNSPECIFIED || heightSize > tallestViewSize) {
- return tallestView;
- } else {
- return smallestView;
- }
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- if (DEBUG) Log.d(TAG, this + " onlayout height: " + (bottom - top));
- mLastActive = mActiveChild;
- int measureSpec = View.MeasureSpec.makeMeasureSpec(bottom - top,
- View.MeasureSpec.EXACTLY);
- mActiveChild = selectActiveChild(measureSpec);
- if (mActiveChild == null) return;
-
- mActiveChild.setVisibility(View.VISIBLE);
-
- if (mLastActive != mActiveChild && mLastActive != null) {
- if (DEBUG) Log.d(TAG, this + " changed children from: " + mLastActive +
- " to: " + mActiveChild);
-
- mEnteringView = mActiveChild;
- mLeavingView = mLastActive;
-
- mEnteringView.setAlpha(1f);
-
- mModestyPanel.setAlpha(1f);
- mModestyPanel.bringToFront();
- mModestyPanelTop = mLeavingView.getHeight();
- mModestyPanel.setVisibility(View.VISIBLE);
- // TODO: mModestyPanel background should be compatible with mLeavingView
-
- mLeavingView.bringToFront();
-
- if (mTransitionAnimation.isRunning()) {
- mTransitionAnimation.cancel();
- }
- mFadeView.setTarget(mLeavingView);
- mFadeView.setFloatValues(0f);
- mFadePanel.setFloatValues(0f);
- mTransitionAnimation.setupStartValues();
- mTransitionAnimation.start();
- }
- final int childWidth = mActiveChild.getMeasuredWidth();
- final int childHeight = mActiveChild.getMeasuredHeight();
- // TODO investigate setting LAYER_TYPE_HARDWARE on mLastActive
- mActiveChild.layout(0, 0, childWidth, childHeight);
-
- if (DEBUG) Log.d(TAG, "got modesty offset of " + mModestyPanelTop);
- mModestyPanel.layout(0, mModestyPanelTop, childWidth, mModestyPanelTop + childHeight);
- }
-
- @Override
- public LayoutParams generateLayoutParams(AttributeSet attrs) {
- if (DEBUG) Log.d(TAG, "generate layout from attrs");
- return new SizeAdaptiveLayout.LayoutParams(getContext(), attrs);
- }
-
- @Override
- protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
- if (DEBUG) Log.d(TAG, "generate default layout from viewgroup");
- return new SizeAdaptiveLayout.LayoutParams(p);
- }
-
- @Override
- protected LayoutParams generateDefaultLayoutParams() {
- if (DEBUG) Log.d(TAG, "generate default layout from null");
- return new SizeAdaptiveLayout.LayoutParams();
- }
-
- @Override
- protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
- return p instanceof SizeAdaptiveLayout.LayoutParams;
- }
-
- /**
- * Per-child layout information associated with ViewSizeAdaptiveLayout.
- *
- * TODO extend to width and height
- *
- * @attr ref android.R.styleable#SizeAdaptiveLayout_Layout_layout_minHeight
- * @attr ref android.R.styleable#SizeAdaptiveLayout_Layout_layout_maxHeight
- */
- public static class LayoutParams extends ViewGroup.LayoutParams {
-
- /**
- * Indicates the minimum valid height for the child.
- */
- @ViewDebug.ExportedProperty(category = "layout")
- public int minHeight;
-
- /**
- * Indicates the maximum valid height for the child.
- */
- @ViewDebug.ExportedProperty(category = "layout")
- public int maxHeight;
-
- /**
- * Constant value for maxHeight that indicates there is not maximum height.
- */
- public static final int UNBOUNDED = -1;
-
- /**
- * {@inheritDoc}
- */
- public LayoutParams(Context c, AttributeSet attrs) {
- super(c, attrs);
- if (DEBUG) {
- Log.d(TAG, "construct layout from attrs");
- for (int i = 0; i < attrs.getAttributeCount(); i++) {
- Log.d(TAG, " " + attrs.getAttributeName(i) + " = " +
- attrs.getAttributeValue(i));
- }
- }
- TypedArray a =
- c.obtainStyledAttributes(attrs,
- R.styleable.SizeAdaptiveLayout_Layout);
-
- minHeight = a.getDimensionPixelSize(MIN_VALID_HEIGHT, 0);
- if (DEBUG) Log.d(TAG, "got minHeight of: " + minHeight);
-
- try {
- maxHeight = a.getLayoutDimension(MAX_VALID_HEIGHT, UNBOUNDED);
- if (DEBUG) Log.d(TAG, "got maxHeight of: " + maxHeight);
- } catch (Exception e) {
- if (DEBUG) Log.d(TAG, "caught exception looking for maxValidHeight " + e);
- }
-
- a.recycle();
- }
-
- /**
- * Creates a new set of layout parameters with the specified width, height
- * and valid height bounds.
- *
- * @param width the width, either {@link #MATCH_PARENT},
- * {@link #WRAP_CONTENT} or a fixed size in pixels
- * @param height the height, either {@link #MATCH_PARENT},
- * {@link #WRAP_CONTENT} or a fixed size in pixels
- * @param minHeight the minimum height of this child
- * @param maxHeight the maximum height of this child
- * or {@link #UNBOUNDED} if the child can grow forever
- */
- public LayoutParams(int width, int height, int minHeight, int maxHeight) {
- super(width, height);
- this.minHeight = minHeight;
- this.maxHeight = maxHeight;
- }
-
- /**
- * {@inheritDoc}
- */
- public LayoutParams(int width, int height) {
- this(width, height, UNBOUNDED, UNBOUNDED);
- }
-
- /**
- * Constructs a new LayoutParams with default values as defined in {@link LayoutParams}.
- */
- public LayoutParams() {
- this(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
- }
-
- /**
- * {@inheritDoc}
- */
- public LayoutParams(ViewGroup.LayoutParams p) {
- super(p);
- minHeight = UNBOUNDED;
- maxHeight = UNBOUNDED;
- }
-
- public String debug(String output) {
- return output + "SizeAdaptiveLayout.LayoutParams={" +
- ", max=" + maxHeight +
- ", max=" + minHeight + "}";
- }
- }
-
- class BringToFrontOnEnd implements AnimatorListener {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (mCanceledAnimationCount == 0) {
- mLeavingView.setVisibility(View.GONE);
- mModestyPanel.setVisibility(View.GONE);
- mEnteringView.bringToFront();
- mEnteringView = null;
- mLeavingView = null;
- } else {
- mCanceledAnimationCount--;
- }
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- mCanceledAnimationCount++;
- }
-
- @Override
- public void onAnimationRepeat(Animator animation) {
- if (DEBUG) Log.d(TAG, "fade animation repeated: should never happen.");
- assert(false);
- }
-
- @Override
- public void onAnimationStart(Animator animation) {
- }
- }
-}
diff --git a/core/java/com/android/internal/widget/WaveView.java b/core/java/com/android/internal/widget/WaveView.java
deleted file mode 100644
index 9e7a649..0000000
--- a/core/java/com/android/internal/widget/WaveView.java
+++ /dev/null
@@ -1,663 +0,0 @@
-/*
- * Copyright (C) 2010 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 com.android.internal.widget;
-
-import java.util.ArrayList;
-
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.drawable.BitmapDrawable;
-import android.media.AudioAttributes;
-import android.os.UserHandle;
-import android.os.Vibrator;
-import android.provider.Settings;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-
-import com.android.internal.R;
-
-/**
- * A special widget containing a center and outer ring. Moving the center ring to the outer ring
- * causes an event that can be caught by implementing OnTriggerListener.
- */
-public class WaveView extends View implements ValueAnimator.AnimatorUpdateListener {
- private static final String TAG = "WaveView";
- private static final boolean DBG = false;
- private static final int WAVE_COUNT = 20; // default wave count
- private static final long VIBRATE_SHORT = 20; // msec
- private static final long VIBRATE_LONG = 20; // msec
-
- // Lock state machine states
- private static final int STATE_RESET_LOCK = 0;
- private static final int STATE_READY = 1;
- private static final int STATE_START_ATTEMPT = 2;
- private static final int STATE_ATTEMPTING = 3;
- private static final int STATE_UNLOCK_ATTEMPT = 4;
- private static final int STATE_UNLOCK_SUCCESS = 5;
-
- // Animation properties.
- private static final long DURATION = 300; // duration of transitional animations
- private static final long FINAL_DURATION = 200; // duration of final animations when unlocking
- private static final long RING_DELAY = 1300; // when to start fading animated rings
- private static final long FINAL_DELAY = 200; // delay for unlock success animation
- private static final long SHORT_DELAY = 100; // for starting one animation after another.
- private static final long WAVE_DURATION = 2000; // amount of time for way to expand/decay
- private static final long RESET_TIMEOUT = 3000; // elapsed time of inactivity before we reset
- private static final long DELAY_INCREMENT = 15; // increment per wave while tracking motion
- private static final long DELAY_INCREMENT2 = 12; // increment per wave while not tracking
- private static final long WAVE_DELAY = WAVE_DURATION / WAVE_COUNT; // initial propagation delay
-
- /**
- * The scale by which to multiply the unlock handle width to compute the radius
- * in which it can be grabbed when accessibility is disabled.
- */
- private static final float GRAB_HANDLE_RADIUS_SCALE_ACCESSIBILITY_DISABLED = 0.5f;
-
- /**
- * The scale by which to multiply the unlock handle width to compute the radius
- * in which it can be grabbed when accessibility is enabled (more generous).
- */
- private static final float GRAB_HANDLE_RADIUS_SCALE_ACCESSIBILITY_ENABLED = 1.0f;
-
- private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
- .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
- .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
- .build();
-
- private Vibrator mVibrator;
- private OnTriggerListener mOnTriggerListener;
- private ArrayList<DrawableHolder> mDrawables = new ArrayList<DrawableHolder>(3);
- private ArrayList<DrawableHolder> mLightWaves = new ArrayList<DrawableHolder>(WAVE_COUNT);
- private boolean mFingerDown = false;
- private float mRingRadius = 182.0f; // Radius of bitmap ring. Used to snap halo to it
- private int mSnapRadius = 136; // minimum threshold for drag unlock
- private int mWaveCount = WAVE_COUNT; // number of waves
- private long mWaveTimerDelay = WAVE_DELAY;
- private int mCurrentWave = 0;
- private float mLockCenterX; // center of widget as dictated by widget size
- private float mLockCenterY;
- private float mMouseX; // current mouse position as of last touch event
- private float mMouseY;
- private DrawableHolder mUnlockRing;
- private DrawableHolder mUnlockDefault;
- private DrawableHolder mUnlockHalo;
- private int mLockState = STATE_RESET_LOCK;
- private int mGrabbedState = OnTriggerListener.NO_HANDLE;
- private boolean mWavesRunning;
- private boolean mFinishWaves;
-
- public WaveView(Context context) {
- this(context, null);
- }
-
- public WaveView(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- // TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.WaveView);
- // mOrientation = a.getInt(R.styleable.WaveView_orientation, HORIZONTAL);
- // a.recycle();
-
- initDrawables();
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- mLockCenterX = 0.5f * w;
- mLockCenterY = 0.5f * h;
- super.onSizeChanged(w, h, oldw, oldh);
- }
-
- @Override
- protected int getSuggestedMinimumWidth() {
- // View should be large enough to contain the unlock ring + halo
- return mUnlockRing.getWidth() + mUnlockHalo.getWidth();
- }
-
- @Override
- protected int getSuggestedMinimumHeight() {
- // View should be large enough to contain the unlock ring + halo
- return mUnlockRing.getHeight() + mUnlockHalo.getHeight();
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
- int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
- int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
- int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
- int width;
- int height;
-
- if (widthSpecMode == MeasureSpec.AT_MOST) {
- width = Math.min(widthSpecSize, getSuggestedMinimumWidth());
- } else if (widthSpecMode == MeasureSpec.EXACTLY) {
- width = widthSpecSize;
- } else {
- width = getSuggestedMinimumWidth();
- }
-
- if (heightSpecMode == MeasureSpec.AT_MOST) {
- height = Math.min(heightSpecSize, getSuggestedMinimumWidth());
- } else if (heightSpecMode == MeasureSpec.EXACTLY) {
- height = heightSpecSize;
- } else {
- height = getSuggestedMinimumHeight();
- }
-
- setMeasuredDimension(width, height);
- }
-
- private void initDrawables() {
- mUnlockRing = new DrawableHolder(createDrawable(R.drawable.unlock_ring));
- mUnlockRing.setX(mLockCenterX);
- mUnlockRing.setY(mLockCenterY);
- mUnlockRing.setScaleX(0.1f);
- mUnlockRing.setScaleY(0.1f);
- mUnlockRing.setAlpha(0.0f);
- mDrawables.add(mUnlockRing);
-
- mUnlockDefault = new DrawableHolder(createDrawable(R.drawable.unlock_default));
- mUnlockDefault.setX(mLockCenterX);
- mUnlockDefault.setY(mLockCenterY);
- mUnlockDefault.setScaleX(0.1f);
- mUnlockDefault.setScaleY(0.1f);
- mUnlockDefault.setAlpha(0.0f);
- mDrawables.add(mUnlockDefault);
-
- mUnlockHalo = new DrawableHolder(createDrawable(R.drawable.unlock_halo));
- mUnlockHalo.setX(mLockCenterX);
- mUnlockHalo.setY(mLockCenterY);
- mUnlockHalo.setScaleX(0.1f);
- mUnlockHalo.setScaleY(0.1f);
- mUnlockHalo.setAlpha(0.0f);
- mDrawables.add(mUnlockHalo);
-
- BitmapDrawable wave = createDrawable(R.drawable.unlock_wave);
- for (int i = 0; i < mWaveCount; i++) {
- DrawableHolder holder = new DrawableHolder(wave);
- mLightWaves.add(holder);
- holder.setAlpha(0.0f);
- }
- }
-
- private void waveUpdateFrame(float mouseX, float mouseY, boolean fingerDown) {
- double distX = mouseX - mLockCenterX;
- double distY = mouseY - mLockCenterY;
- int dragDistance = (int) Math.ceil(Math.hypot(distX, distY));
- double touchA = Math.atan2(distX, distY);
- float ringX = (float) (mLockCenterX + mRingRadius * Math.sin(touchA));
- float ringY = (float) (mLockCenterY + mRingRadius * Math.cos(touchA));
-
- switch (mLockState) {
- case STATE_RESET_LOCK:
- if (DBG) Log.v(TAG, "State RESET_LOCK");
- mWaveTimerDelay = WAVE_DELAY;
- for (int i = 0; i < mLightWaves.size(); i++) {
- DrawableHolder holder = mLightWaves.get(i);
- holder.addAnimTo(300, 0, "alpha", 0.0f, false);
- }
- for (int i = 0; i < mLightWaves.size(); i++) {
- mLightWaves.get(i).startAnimations(this);
- }
-
- mUnlockRing.addAnimTo(DURATION, 0, "x", mLockCenterX, true);
- mUnlockRing.addAnimTo(DURATION, 0, "y", mLockCenterY, true);
- mUnlockRing.addAnimTo(DURATION, 0, "scaleX", 0.1f, true);
- mUnlockRing.addAnimTo(DURATION, 0, "scaleY", 0.1f, true);
- mUnlockRing.addAnimTo(DURATION, 0, "alpha", 0.0f, true);
-
- mUnlockDefault.removeAnimationFor("x");
- mUnlockDefault.removeAnimationFor("y");
- mUnlockDefault.removeAnimationFor("scaleX");
- mUnlockDefault.removeAnimationFor("scaleY");
- mUnlockDefault.removeAnimationFor("alpha");
- mUnlockDefault.setX(mLockCenterX);
- mUnlockDefault.setY(mLockCenterY);
- mUnlockDefault.setScaleX(0.1f);
- mUnlockDefault.setScaleY(0.1f);
- mUnlockDefault.setAlpha(0.0f);
- mUnlockDefault.addAnimTo(DURATION, SHORT_DELAY, "scaleX", 1.0f, true);
- mUnlockDefault.addAnimTo(DURATION, SHORT_DELAY, "scaleY", 1.0f, true);
- mUnlockDefault.addAnimTo(DURATION, SHORT_DELAY, "alpha", 1.0f, true);
-
- mUnlockHalo.removeAnimationFor("x");
- mUnlockHalo.removeAnimationFor("y");
- mUnlockHalo.removeAnimationFor("scaleX");
- mUnlockHalo.removeAnimationFor("scaleY");
- mUnlockHalo.removeAnimationFor("alpha");
- mUnlockHalo.setX(mLockCenterX);
- mUnlockHalo.setY(mLockCenterY);
- mUnlockHalo.setScaleX(0.1f);
- mUnlockHalo.setScaleY(0.1f);
- mUnlockHalo.setAlpha(0.0f);
- mUnlockHalo.addAnimTo(DURATION, SHORT_DELAY, "x", mLockCenterX, true);
- mUnlockHalo.addAnimTo(DURATION, SHORT_DELAY, "y", mLockCenterY, true);
- mUnlockHalo.addAnimTo(DURATION, SHORT_DELAY, "scaleX", 1.0f, true);
- mUnlockHalo.addAnimTo(DURATION, SHORT_DELAY, "scaleY", 1.0f, true);
- mUnlockHalo.addAnimTo(DURATION, SHORT_DELAY, "alpha", 1.0f, true);
-
- removeCallbacks(mLockTimerActions);
-
- mLockState = STATE_READY;
- break;
-
- case STATE_READY:
- if (DBG) Log.v(TAG, "State READY");
- mWaveTimerDelay = WAVE_DELAY;
- break;
-
- case STATE_START_ATTEMPT:
- if (DBG) Log.v(TAG, "State START_ATTEMPT");
- mUnlockDefault.removeAnimationFor("x");
- mUnlockDefault.removeAnimationFor("y");
- mUnlockDefault.removeAnimationFor("scaleX");
- mUnlockDefault.removeAnimationFor("scaleY");
- mUnlockDefault.removeAnimationFor("alpha");
- mUnlockDefault.setX(mLockCenterX + 182);
- mUnlockDefault.setY(mLockCenterY);
- mUnlockDefault.setScaleX(0.1f);
- mUnlockDefault.setScaleY(0.1f);
- mUnlockDefault.setAlpha(0.0f);
-
- mUnlockDefault.addAnimTo(DURATION, SHORT_DELAY, "scaleX", 1.0f, false);
- mUnlockDefault.addAnimTo(DURATION, SHORT_DELAY, "scaleY", 1.0f, false);
- mUnlockDefault.addAnimTo(DURATION, SHORT_DELAY, "alpha", 1.0f, false);
-
- mUnlockRing.addAnimTo(DURATION, 0, "scaleX", 1.0f, true);
- mUnlockRing.addAnimTo(DURATION, 0, "scaleY", 1.0f, true);
- mUnlockRing.addAnimTo(DURATION, 0, "alpha", 1.0f, true);
-
- mLockState = STATE_ATTEMPTING;
- break;
-
- case STATE_ATTEMPTING:
- if (DBG) Log.v(TAG, "State ATTEMPTING (fingerDown = " + fingerDown + ")");
- if (dragDistance > mSnapRadius) {
- mFinishWaves = true; // don't start any more waves.
- if (fingerDown) {
- mUnlockHalo.addAnimTo(0, 0, "x", ringX, true);
- mUnlockHalo.addAnimTo(0, 0, "y", ringY, true);
- mUnlockHalo.addAnimTo(0, 0, "scaleX", 1.0f, true);
- mUnlockHalo.addAnimTo(0, 0, "scaleY", 1.0f, true);
- mUnlockHalo.addAnimTo(0, 0, "alpha", 1.0f, true);
- } else {
- if (DBG) Log.v(TAG, "up detected, moving to STATE_UNLOCK_ATTEMPT");
- mLockState = STATE_UNLOCK_ATTEMPT;
- }
- } else {
- // If waves have stopped, we need to kick them off again...
- if (!mWavesRunning) {
- mWavesRunning = true;
- mFinishWaves = false;
- // mWaveTimerDelay = WAVE_DELAY;
- postDelayed(mAddWaveAction, mWaveTimerDelay);
- }
- mUnlockHalo.addAnimTo(0, 0, "x", mouseX, true);
- mUnlockHalo.addAnimTo(0, 0, "y", mouseY, true);
- mUnlockHalo.addAnimTo(0, 0, "scaleX", 1.0f, true);
- mUnlockHalo.addAnimTo(0, 0, "scaleY", 1.0f, true);
- mUnlockHalo.addAnimTo(0, 0, "alpha", 1.0f, true);
- }
- break;
-
- case STATE_UNLOCK_ATTEMPT:
- if (DBG) Log.v(TAG, "State UNLOCK_ATTEMPT");
- if (dragDistance > mSnapRadius) {
- for (int n = 0; n < mLightWaves.size(); n++) {
- DrawableHolder wave = mLightWaves.get(n);
- long delay = 1000L*(6 + n - mCurrentWave)/10L;
- wave.addAnimTo(FINAL_DURATION, delay, "x", ringX, true);
- wave.addAnimTo(FINAL_DURATION, delay, "y", ringY, true);
- wave.addAnimTo(FINAL_DURATION, delay, "scaleX", 0.1f, true);
- wave.addAnimTo(FINAL_DURATION, delay, "scaleY", 0.1f, true);
- wave.addAnimTo(FINAL_DURATION, delay, "alpha", 0.0f, true);
- }
- for (int i = 0; i < mLightWaves.size(); i++) {
- mLightWaves.get(i).startAnimations(this);
- }
-
- mUnlockRing.addAnimTo(FINAL_DURATION, 0, "x", ringX, false);
- mUnlockRing.addAnimTo(FINAL_DURATION, 0, "y", ringY, false);
- mUnlockRing.addAnimTo(FINAL_DURATION, 0, "scaleX", 0.1f, false);
- mUnlockRing.addAnimTo(FINAL_DURATION, 0, "scaleY", 0.1f, false);
- mUnlockRing.addAnimTo(FINAL_DURATION, 0, "alpha", 0.0f, false);
-
- mUnlockRing.addAnimTo(FINAL_DURATION, FINAL_DELAY, "alpha", 0.0f, false);
-
- mUnlockDefault.removeAnimationFor("x");
- mUnlockDefault.removeAnimationFor("y");
- mUnlockDefault.removeAnimationFor("scaleX");
- mUnlockDefault.removeAnimationFor("scaleY");
- mUnlockDefault.removeAnimationFor("alpha");
- mUnlockDefault.setX(ringX);
- mUnlockDefault.setY(ringY);
- mUnlockDefault.setScaleX(0.1f);
- mUnlockDefault.setScaleY(0.1f);
- mUnlockDefault.setAlpha(0.0f);
-
- mUnlockDefault.addAnimTo(FINAL_DURATION, 0, "x", ringX, true);
- mUnlockDefault.addAnimTo(FINAL_DURATION, 0, "y", ringY, true);
- mUnlockDefault.addAnimTo(FINAL_DURATION, 0, "scaleX", 1.0f, true);
- mUnlockDefault.addAnimTo(FINAL_DURATION, 0, "scaleY", 1.0f, true);
- mUnlockDefault.addAnimTo(FINAL_DURATION, 0, "alpha", 1.0f, true);
-
- mUnlockDefault.addAnimTo(FINAL_DURATION, FINAL_DELAY, "scaleX", 3.0f, false);
- mUnlockDefault.addAnimTo(FINAL_DURATION, FINAL_DELAY, "scaleY", 3.0f, false);
- mUnlockDefault.addAnimTo(FINAL_DURATION, FINAL_DELAY, "alpha", 0.0f, false);
-
- mUnlockHalo.addAnimTo(FINAL_DURATION, 0, "x", ringX, false);
- mUnlockHalo.addAnimTo(FINAL_DURATION, 0, "y", ringY, false);
-
- mUnlockHalo.addAnimTo(FINAL_DURATION, FINAL_DELAY, "scaleX", 3.0f, false);
- mUnlockHalo.addAnimTo(FINAL_DURATION, FINAL_DELAY, "scaleY", 3.0f, false);
- mUnlockHalo.addAnimTo(FINAL_DURATION, FINAL_DELAY, "alpha", 0.0f, false);
-
- removeCallbacks(mLockTimerActions);
-
- postDelayed(mLockTimerActions, RESET_TIMEOUT);
-
- dispatchTriggerEvent(OnTriggerListener.CENTER_HANDLE);
- mLockState = STATE_UNLOCK_SUCCESS;
- } else {
- mLockState = STATE_RESET_LOCK;
- }
- break;
-
- case STATE_UNLOCK_SUCCESS:
- if (DBG) Log.v(TAG, "State UNLOCK_SUCCESS");
- removeCallbacks(mAddWaveAction);
- break;
-
- default:
- if (DBG) Log.v(TAG, "Unknown state " + mLockState);
- break;
- }
- mUnlockDefault.startAnimations(this);
- mUnlockHalo.startAnimations(this);
- mUnlockRing.startAnimations(this);
- }
-
- BitmapDrawable createDrawable(int resId) {
- Resources res = getResources();
- Bitmap bitmap = BitmapFactory.decodeResource(res, resId);
- return new BitmapDrawable(res, bitmap);
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- waveUpdateFrame(mMouseX, mMouseY, mFingerDown);
- for (int i = 0; i < mDrawables.size(); ++i) {
- mDrawables.get(i).draw(canvas);
- }
- for (int i = 0; i < mLightWaves.size(); ++i) {
- mLightWaves.get(i).draw(canvas);
- }
- }
-
- private final Runnable mLockTimerActions = new Runnable() {
- public void run() {
- if (DBG) Log.v(TAG, "LockTimerActions");
- // reset lock after inactivity
- if (mLockState == STATE_ATTEMPTING) {
- if (DBG) Log.v(TAG, "Timer resets to STATE_RESET_LOCK");
- mLockState = STATE_RESET_LOCK;
- }
- // for prototype, reset after successful unlock
- if (mLockState == STATE_UNLOCK_SUCCESS) {
- if (DBG) Log.v(TAG, "Timer resets to STATE_RESET_LOCK after success");
- mLockState = STATE_RESET_LOCK;
- }
- invalidate();
- }
- };
-
- private final Runnable mAddWaveAction = new Runnable() {
- public void run() {
- double distX = mMouseX - mLockCenterX;
- double distY = mMouseY - mLockCenterY;
- int dragDistance = (int) Math.ceil(Math.hypot(distX, distY));
- if (mLockState == STATE_ATTEMPTING && dragDistance < mSnapRadius
- && mWaveTimerDelay >= WAVE_DELAY) {
- mWaveTimerDelay = Math.min(WAVE_DURATION, mWaveTimerDelay + DELAY_INCREMENT);
-
- DrawableHolder wave = mLightWaves.get(mCurrentWave);
- wave.setAlpha(0.0f);
- wave.setScaleX(0.2f);
- wave.setScaleY(0.2f);
- wave.setX(mMouseX);
- wave.setY(mMouseY);
-
- wave.addAnimTo(WAVE_DURATION, 0, "x", mLockCenterX, true);
- wave.addAnimTo(WAVE_DURATION, 0, "y", mLockCenterY, true);
- wave.addAnimTo(WAVE_DURATION*2/3, 0, "alpha", 1.0f, true);
- wave.addAnimTo(WAVE_DURATION, 0, "scaleX", 1.0f, true);
- wave.addAnimTo(WAVE_DURATION, 0, "scaleY", 1.0f, true);
-
- wave.addAnimTo(1000, RING_DELAY, "alpha", 0.0f, false);
- wave.startAnimations(WaveView.this);
-
- mCurrentWave = (mCurrentWave+1) % mWaveCount;
- if (DBG) Log.v(TAG, "WaveTimerDelay: start new wave in " + mWaveTimerDelay);
- } else {
- mWaveTimerDelay += DELAY_INCREMENT2;
- }
- if (mFinishWaves) {
- // sentinel used to restart the waves after they've stopped
- mWavesRunning = false;
- } else {
- postDelayed(mAddWaveAction, mWaveTimerDelay);
- }
- }
- };
-
- @Override
- public boolean onHoverEvent(MotionEvent event) {
- if (AccessibilityManager.getInstance(mContext).isTouchExplorationEnabled()) {
- final int action = event.getAction();
- switch (action) {
- case MotionEvent.ACTION_HOVER_ENTER:
- event.setAction(MotionEvent.ACTION_DOWN);
- break;
- case MotionEvent.ACTION_HOVER_MOVE:
- event.setAction(MotionEvent.ACTION_MOVE);
- break;
- case MotionEvent.ACTION_HOVER_EXIT:
- event.setAction(MotionEvent.ACTION_UP);
- break;
- }
- onTouchEvent(event);
- event.setAction(action);
- }
- return super.onHoverEvent(event);
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- final int action = event.getAction();
- mMouseX = event.getX();
- mMouseY = event.getY();
- boolean handled = false;
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- removeCallbacks(mLockTimerActions);
- mFingerDown = true;
- tryTransitionToStartAttemptState(event);
- handled = true;
- break;
-
- case MotionEvent.ACTION_MOVE:
- tryTransitionToStartAttemptState(event);
- handled = true;
- break;
-
- case MotionEvent.ACTION_UP:
- if (DBG) Log.v(TAG, "ACTION_UP");
- mFingerDown = false;
- postDelayed(mLockTimerActions, RESET_TIMEOUT);
- setGrabbedState(OnTriggerListener.NO_HANDLE);
- // Normally the state machine is driven by user interaction causing redraws.
- // However, when there's no more user interaction and no running animations,
- // the state machine stops advancing because onDraw() never gets called.
- // The following ensures we advance to the next state in this case,
- // either STATE_UNLOCK_ATTEMPT or STATE_RESET_LOCK.
- waveUpdateFrame(mMouseX, mMouseY, mFingerDown);
- handled = true;
- break;
-
- case MotionEvent.ACTION_CANCEL:
- mFingerDown = false;
- handled = true;
- break;
- }
- invalidate();
- return handled ? true : super.onTouchEvent(event);
- }
-
- /**
- * Tries to transition to start attempt state.
- *
- * @param event A motion event.
- */
- private void tryTransitionToStartAttemptState(MotionEvent event) {
- final float dx = event.getX() - mUnlockHalo.getX();
- final float dy = event.getY() - mUnlockHalo.getY();
- float dist = (float) Math.hypot(dx, dy);
- if (dist <= getScaledGrabHandleRadius()) {
- setGrabbedState(OnTriggerListener.CENTER_HANDLE);
- if (mLockState == STATE_READY) {
- mLockState = STATE_START_ATTEMPT;
- if (AccessibilityManager.getInstance(mContext).isEnabled()) {
- announceUnlockHandle();
- }
- }
- }
- }
-
- /**
- * @return The radius in which the handle is grabbed scaled based on
- * whether accessibility is enabled.
- */
- private float getScaledGrabHandleRadius() {
- if (AccessibilityManager.getInstance(mContext).isEnabled()) {
- return GRAB_HANDLE_RADIUS_SCALE_ACCESSIBILITY_ENABLED * mUnlockHalo.getWidth();
- } else {
- return GRAB_HANDLE_RADIUS_SCALE_ACCESSIBILITY_DISABLED * mUnlockHalo.getWidth();
- }
- }
-
- /**
- * Announces the unlock handle if accessibility is enabled.
- */
- private void announceUnlockHandle() {
- setContentDescription(mContext.getString(R.string.description_target_unlock_tablet));
- sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
- setContentDescription(null);
- }
-
- /**
- * Triggers haptic feedback.
- */
- private synchronized void vibrate(long duration) {
- final boolean hapticEnabled = Settings.System.getIntForUser(
- mContext.getContentResolver(), Settings.System.HAPTIC_FEEDBACK_ENABLED, 1,
- UserHandle.USER_CURRENT) != 0;
- if (hapticEnabled) {
- if (mVibrator == null) {
- mVibrator = (android.os.Vibrator) getContext()
- .getSystemService(Context.VIBRATOR_SERVICE);
- }
- mVibrator.vibrate(duration, VIBRATION_ATTRIBUTES);
- }
- }
-
- /**
- * Registers a callback to be invoked when the user triggers an event.
- *
- * @param listener the OnDialTriggerListener to attach to this view
- */
- public void setOnTriggerListener(OnTriggerListener listener) {
- mOnTriggerListener = listener;
- }
-
- /**
- * Dispatches a trigger event to listener. Ignored if a listener is not set.
- * @param whichHandle the handle that triggered the event.
- */
- private void dispatchTriggerEvent(int whichHandle) {
- vibrate(VIBRATE_LONG);
- if (mOnTriggerListener != null) {
- mOnTriggerListener.onTrigger(this, whichHandle);
- }
- }
-
- /**
- * Sets the current grabbed state, and dispatches a grabbed state change
- * event to our listener.
- */
- private void setGrabbedState(int newState) {
- if (newState != mGrabbedState) {
- mGrabbedState = newState;
- if (mOnTriggerListener != null) {
- mOnTriggerListener.onGrabbedStateChange(this, mGrabbedState);
- }
- }
- }
-
- public interface OnTriggerListener {
- /**
- * Sent when the user releases the handle.
- */
- public static final int NO_HANDLE = 0;
-
- /**
- * Sent when the user grabs the center handle
- */
- public static final int CENTER_HANDLE = 10;
-
- /**
- * Called when the user drags the center ring beyond a threshold.
- */
- void onTrigger(View v, int whichHandle);
-
- /**
- * Called when the "grabbed state" changes (i.e. when the user either grabs or releases
- * one of the handles.)
- *
- * @param v the view that was triggered
- * @param grabbedState the new state: {@link #NO_HANDLE}, {@link #CENTER_HANDLE},
- */
- void onGrabbedStateChange(View v, int grabbedState);
- }
-
- public void onAnimationUpdate(ValueAnimator animation) {
- invalidate();
- }
-
- public void reset() {
- if (DBG) Log.v(TAG, "reset() : resets state to STATE_RESET_LOCK");
- mLockState = STATE_RESET_LOCK;
- invalidate();
- }
-}
diff --git a/core/java/com/android/internal/widget/multiwaveview/Ease.java b/core/java/com/android/internal/widget/multiwaveview/Ease.java
deleted file mode 100644
index 7f90c44..0000000
--- a/core/java/com/android/internal/widget/multiwaveview/Ease.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.widget.multiwaveview;
-
-import android.animation.TimeInterpolator;
-
-class Ease {
- private static final float DOMAIN = 1.0f;
- private static final float DURATION = 1.0f;
- private static final float START = 0.0f;
-
- static class Linear {
- public static final TimeInterpolator easeNone = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return input;
- }
- };
- }
-
- static class Cubic {
- public static final TimeInterpolator easeIn = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return DOMAIN*(input/=DURATION)*input*input + START;
- }
- };
- public static final TimeInterpolator easeOut = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return DOMAIN*((input=input/DURATION-1)*input*input + 1) + START;
- }
- };
- public static final TimeInterpolator easeInOut = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return ((input/=DURATION/2) < 1.0f) ?
- (DOMAIN/2*input*input*input + START)
- : (DOMAIN/2*((input-=2)*input*input + 2) + START);
- }
- };
- }
-
- static class Quad {
- public static final TimeInterpolator easeIn = new TimeInterpolator() {
- public float getInterpolation (float input) {
- return DOMAIN*(input/=DURATION)*input + START;
- }
- };
- public static final TimeInterpolator easeOut = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return -DOMAIN *(input/=DURATION)*(input-2) + START;
- }
- };
- public static final TimeInterpolator easeInOut = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return ((input/=DURATION/2) < 1) ?
- (DOMAIN/2*input*input + START)
- : (-DOMAIN/2 * ((--input)*(input-2) - 1) + START);
- }
- };
- }
-
- static class Quart {
- public static final TimeInterpolator easeIn = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return DOMAIN*(input/=DURATION)*input*input*input + START;
- }
- };
- public static final TimeInterpolator easeOut = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return -DOMAIN * ((input=input/DURATION-1)*input*input*input - 1) + START;
- }
- };
- public static final TimeInterpolator easeInOut = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return ((input/=DURATION/2) < 1) ?
- (DOMAIN/2*input*input*input*input + START)
- : (-DOMAIN/2 * ((input-=2)*input*input*input - 2) + START);
- }
- };
- }
-
- static class Quint {
- public static final TimeInterpolator easeIn = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return DOMAIN*(input/=DURATION)*input*input*input*input + START;
- }
- };
- public static final TimeInterpolator easeOut = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return DOMAIN*((input=input/DURATION-1)*input*input*input*input + 1) + START;
- }
- };
- public static final TimeInterpolator easeInOut = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return ((input/=DURATION/2) < 1) ?
- (DOMAIN/2*input*input*input*input*input + START)
- : (DOMAIN/2*((input-=2)*input*input*input*input + 2) + START);
- }
- };
- }
-
- static class Sine {
- public static final TimeInterpolator easeIn = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return -DOMAIN * (float) Math.cos(input/DURATION * (Math.PI/2)) + DOMAIN + START;
- }
- };
- public static final TimeInterpolator easeOut = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return DOMAIN * (float) Math.sin(input/DURATION * (Math.PI/2)) + START;
- }
- };
- public static final TimeInterpolator easeInOut = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return -DOMAIN/2 * ((float)Math.cos(Math.PI*input/DURATION) - 1.0f) + START;
- }
- };
- }
-
-}
diff --git a/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java b/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java
deleted file mode 100644
index 11ac19e..0000000
--- a/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java
+++ /dev/null
@@ -1,1383 +0,0 @@
-/*
- * Copyright (C) 2012 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 com.android.internal.widget.multiwaveview;
-
-import android.animation.Animator;
-import android.animation.Animator.AnimatorListener;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.media.AudioAttributes;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.os.Vibrator;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.TypedValue;
-import android.view.Gravity;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.accessibility.AccessibilityManager;
-
-import com.android.internal.R;
-
-import java.util.ArrayList;
-
-/**
- * A re-usable widget containing a center, outer ring and wave animation.
- */
-public class GlowPadView extends View {
- private static final String TAG = "GlowPadView";
- private static final boolean DEBUG = false;
-
- // Wave state machine
- private static final int STATE_IDLE = 0;
- private static final int STATE_START = 1;
- private static final int STATE_FIRST_TOUCH = 2;
- private static final int STATE_TRACKING = 3;
- private static final int STATE_SNAP = 4;
- private static final int STATE_FINISH = 5;
-
- // Animation properties.
- private static final float SNAP_MARGIN_DEFAULT = 20.0f; // distance to ring before we snap to it
-
- public interface OnTriggerListener {
- int NO_HANDLE = 0;
- int CENTER_HANDLE = 1;
- public void onGrabbed(View v, int handle);
- public void onReleased(View v, int handle);
- public void onTrigger(View v, int target);
- public void onGrabbedStateChange(View v, int handle);
- public void onFinishFinalAnimation();
- }
-
- // Tuneable parameters for animation
- private static final int WAVE_ANIMATION_DURATION = 1000;
- private static final int RETURN_TO_HOME_DELAY = 1200;
- private static final int RETURN_TO_HOME_DURATION = 200;
- private static final int HIDE_ANIMATION_DELAY = 200;
- private static final int HIDE_ANIMATION_DURATION = 200;
- private static final int SHOW_ANIMATION_DURATION = 200;
- private static final int SHOW_ANIMATION_DELAY = 50;
- private static final int INITIAL_SHOW_HANDLE_DURATION = 200;
- private static final int REVEAL_GLOW_DELAY = 0;
- private static final int REVEAL_GLOW_DURATION = 0;
-
- private static final float TAP_RADIUS_SCALE_ACCESSIBILITY_ENABLED = 1.3f;
- private static final float TARGET_SCALE_EXPANDED = 1.0f;
- private static final float TARGET_SCALE_COLLAPSED = 0.8f;
- private static final float RING_SCALE_EXPANDED = 1.0f;
- private static final float RING_SCALE_COLLAPSED = 0.5f;
-
- private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
- .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
- .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
- .build();
-
- private ArrayList<TargetDrawable> mTargetDrawables = new ArrayList<TargetDrawable>();
- private AnimationBundle mWaveAnimations = new AnimationBundle();
- private AnimationBundle mTargetAnimations = new AnimationBundle();
- private AnimationBundle mGlowAnimations = new AnimationBundle();
- private ArrayList<String> mTargetDescriptions;
- private ArrayList<String> mDirectionDescriptions;
- private OnTriggerListener mOnTriggerListener;
- private TargetDrawable mHandleDrawable;
- private TargetDrawable mOuterRing;
- private Vibrator mVibrator;
-
- private int mFeedbackCount = 3;
- private int mVibrationDuration = 0;
- private int mGrabbedState;
- private int mActiveTarget = -1;
- private float mGlowRadius;
- private float mWaveCenterX;
- private float mWaveCenterY;
- private int mMaxTargetHeight;
- private int mMaxTargetWidth;
- private float mRingScaleFactor = 1f;
- private boolean mAllowScaling;
-
- private float mOuterRadius = 0.0f;
- private float mSnapMargin = 0.0f;
- private float mFirstItemOffset = 0.0f;
- private boolean mMagneticTargets = false;
- private boolean mDragging;
- private int mNewTargetResources;
-
- private class AnimationBundle extends ArrayList<Tweener> {
- private static final long serialVersionUID = 0xA84D78726F127468L;
- private boolean mSuspended;
-
- public void start() {
- if (mSuspended) return; // ignore attempts to start animations
- final int count = size();
- for (int i = 0; i < count; i++) {
- Tweener anim = get(i);
- anim.animator.start();
- }
- }
-
- public void cancel() {
- final int count = size();
- for (int i = 0; i < count; i++) {
- Tweener anim = get(i);
- anim.animator.cancel();
- }
- clear();
- }
-
- public void stop() {
- final int count = size();
- for (int i = 0; i < count; i++) {
- Tweener anim = get(i);
- anim.animator.end();
- }
- clear();
- }
-
- public void setSuspended(boolean suspend) {
- mSuspended = suspend;
- }
- };
-
- private AnimatorListener mResetListener = new AnimatorListenerAdapter() {
- public void onAnimationEnd(Animator animator) {
- switchToState(STATE_IDLE, mWaveCenterX, mWaveCenterY);
- dispatchOnFinishFinalAnimation();
- }
- };
-
- private AnimatorListener mResetListenerWithPing = new AnimatorListenerAdapter() {
- public void onAnimationEnd(Animator animator) {
- ping();
- switchToState(STATE_IDLE, mWaveCenterX, mWaveCenterY);
- dispatchOnFinishFinalAnimation();
- }
- };
-
- private AnimatorUpdateListener mUpdateListener = new AnimatorUpdateListener() {
- public void onAnimationUpdate(ValueAnimator animation) {
- invalidate();
- }
- };
-
- private boolean mAnimatingTargets;
- private AnimatorListener mTargetUpdateListener = new AnimatorListenerAdapter() {
- public void onAnimationEnd(Animator animator) {
- if (mNewTargetResources != 0) {
- internalSetTargetResources(mNewTargetResources);
- mNewTargetResources = 0;
- hideTargets(false, false);
- }
- mAnimatingTargets = false;
- }
- };
- private int mTargetResourceId;
- private int mTargetDescriptionsResourceId;
- private int mDirectionDescriptionsResourceId;
- private boolean mAlwaysTrackFinger;
- private int mHorizontalInset;
- private int mVerticalInset;
- private int mGravity = Gravity.TOP;
- private boolean mInitialLayout = true;
- private Tweener mBackgroundAnimator;
- private PointCloud mPointCloud;
- private float mInnerRadius;
- private int mPointerId;
-
- public GlowPadView(Context context) {
- this(context, null);
- }
-
- public GlowPadView(Context context, AttributeSet attrs) {
- super(context, attrs);
- Resources res = context.getResources();
-
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GlowPadView);
- mInnerRadius = a.getDimension(R.styleable.GlowPadView_innerRadius, mInnerRadius);
- mOuterRadius = a.getDimension(R.styleable.GlowPadView_outerRadius, mOuterRadius);
- mSnapMargin = a.getDimension(R.styleable.GlowPadView_snapMargin, mSnapMargin);
- mFirstItemOffset = (float) Math.toRadians(
- a.getFloat(R.styleable.GlowPadView_firstItemOffset,
- (float) Math.toDegrees(mFirstItemOffset)));
- mVibrationDuration = a.getInt(R.styleable.GlowPadView_vibrationDuration,
- mVibrationDuration);
- mFeedbackCount = a.getInt(R.styleable.GlowPadView_feedbackCount,
- mFeedbackCount);
- mAllowScaling = a.getBoolean(R.styleable.GlowPadView_allowScaling, false);
- TypedValue handle = a.peekValue(R.styleable.GlowPadView_handleDrawable);
- mHandleDrawable = new TargetDrawable(res, handle != null ? handle.resourceId : 0);
- mHandleDrawable.setState(TargetDrawable.STATE_INACTIVE);
- mOuterRing = new TargetDrawable(res,
- getResourceId(a, R.styleable.GlowPadView_outerRingDrawable));
-
- mAlwaysTrackFinger = a.getBoolean(R.styleable.GlowPadView_alwaysTrackFinger, false);
- mMagneticTargets = a.getBoolean(R.styleable.GlowPadView_magneticTargets, mMagneticTargets);
-
- int pointId = getResourceId(a, R.styleable.GlowPadView_pointDrawable);
- Drawable pointDrawable = pointId != 0 ? context.getDrawable(pointId) : null;
- mGlowRadius = a.getDimension(R.styleable.GlowPadView_glowRadius, 0.0f);
-
- mPointCloud = new PointCloud(pointDrawable);
- mPointCloud.makePointCloud(mInnerRadius, mOuterRadius);
- mPointCloud.glowManager.setRadius(mGlowRadius);
-
- TypedValue outValue = new TypedValue();
-
- // Read array of target drawables
- if (a.getValue(R.styleable.GlowPadView_targetDrawables, outValue)) {
- internalSetTargetResources(outValue.resourceId);
- }
- if (mTargetDrawables == null || mTargetDrawables.size() == 0) {
- throw new IllegalStateException("Must specify at least one target drawable");
- }
-
- // Read array of target descriptions
- if (a.getValue(R.styleable.GlowPadView_targetDescriptions, outValue)) {
- final int resourceId = outValue.resourceId;
- if (resourceId == 0) {
- throw new IllegalStateException("Must specify target descriptions");
- }
- setTargetDescriptionsResourceId(resourceId);
- }
-
- // Read array of direction descriptions
- if (a.getValue(R.styleable.GlowPadView_directionDescriptions, outValue)) {
- final int resourceId = outValue.resourceId;
- if (resourceId == 0) {
- throw new IllegalStateException("Must specify direction descriptions");
- }
- setDirectionDescriptionsResourceId(resourceId);
- }
-
- mGravity = a.getInt(R.styleable.GlowPadView_gravity, Gravity.TOP);
-
- a.recycle();
-
- setVibrateEnabled(mVibrationDuration > 0);
-
- assignDefaultsIfNeeded();
- }
-
- private int getResourceId(TypedArray a, int id) {
- TypedValue tv = a.peekValue(id);
- return tv == null ? 0 : tv.resourceId;
- }
-
- private void dump() {
- Log.v(TAG, "Outer Radius = " + mOuterRadius);
- Log.v(TAG, "SnapMargin = " + mSnapMargin);
- Log.v(TAG, "FeedbackCount = " + mFeedbackCount);
- Log.v(TAG, "VibrationDuration = " + mVibrationDuration);
- Log.v(TAG, "GlowRadius = " + mGlowRadius);
- Log.v(TAG, "WaveCenterX = " + mWaveCenterX);
- Log.v(TAG, "WaveCenterY = " + mWaveCenterY);
- }
-
- public void suspendAnimations() {
- mWaveAnimations.setSuspended(true);
- mTargetAnimations.setSuspended(true);
- mGlowAnimations.setSuspended(true);
- }
-
- public void resumeAnimations() {
- mWaveAnimations.setSuspended(false);
- mTargetAnimations.setSuspended(false);
- mGlowAnimations.setSuspended(false);
- mWaveAnimations.start();
- mTargetAnimations.start();
- mGlowAnimations.start();
- }
-
- @Override
- protected int getSuggestedMinimumWidth() {
- // View should be large enough to contain the background + handle and
- // target drawable on either edge.
- return (int) (Math.max(mOuterRing.getWidth(), 2 * mOuterRadius) + mMaxTargetWidth);
- }
-
- @Override
- protected int getSuggestedMinimumHeight() {
- // View should be large enough to contain the unlock ring + target and
- // target drawable on either edge
- return (int) (Math.max(mOuterRing.getHeight(), 2 * mOuterRadius) + mMaxTargetHeight);
- }
-
- /**
- * This gets the suggested width accounting for the ring's scale factor.
- */
- protected int getScaledSuggestedMinimumWidth() {
- return (int) (mRingScaleFactor * Math.max(mOuterRing.getWidth(), 2 * mOuterRadius)
- + mMaxTargetWidth);
- }
-
- /**
- * This gets the suggested height accounting for the ring's scale factor.
- */
- protected int getScaledSuggestedMinimumHeight() {
- return (int) (mRingScaleFactor * Math.max(mOuterRing.getHeight(), 2 * mOuterRadius)
- + mMaxTargetHeight);
- }
-
- private int resolveMeasured(int measureSpec, int desired)
- {
- int result = 0;
- int specSize = MeasureSpec.getSize(measureSpec);
- switch (MeasureSpec.getMode(measureSpec)) {
- case MeasureSpec.UNSPECIFIED:
- result = desired;
- break;
- case MeasureSpec.AT_MOST:
- result = Math.min(specSize, desired);
- break;
- case MeasureSpec.EXACTLY:
- default:
- result = specSize;
- }
- return result;
- }
-
- private void switchToState(int state, float x, float y) {
- switch (state) {
- case STATE_IDLE:
- deactivateTargets();
- hideGlow(0, 0, 0.0f, null);
- startBackgroundAnimation(0, 0.0f);
- mHandleDrawable.setState(TargetDrawable.STATE_INACTIVE);
- mHandleDrawable.setAlpha(1.0f);
- break;
-
- case STATE_START:
- startBackgroundAnimation(0, 0.0f);
- break;
-
- case STATE_FIRST_TOUCH:
- mHandleDrawable.setAlpha(0.0f);
- deactivateTargets();
- showTargets(true);
- startBackgroundAnimation(INITIAL_SHOW_HANDLE_DURATION, 1.0f);
- setGrabbedState(OnTriggerListener.CENTER_HANDLE);
- if (AccessibilityManager.getInstance(mContext).isEnabled()) {
- announceTargets();
- }
- break;
-
- case STATE_TRACKING:
- mHandleDrawable.setAlpha(0.0f);
- showGlow(REVEAL_GLOW_DURATION , REVEAL_GLOW_DELAY, 1.0f, null);
- break;
-
- case STATE_SNAP:
- // TODO: Add transition states (see list_selector_background_transition.xml)
- mHandleDrawable.setAlpha(0.0f);
- showGlow(REVEAL_GLOW_DURATION , REVEAL_GLOW_DELAY, 0.0f, null);
- break;
-
- case STATE_FINISH:
- doFinish();
- break;
- }
- }
-
- private void showGlow(int duration, int delay, float finalAlpha,
- AnimatorListener finishListener) {
- mGlowAnimations.cancel();
- mGlowAnimations.add(Tweener.to(mPointCloud.glowManager, duration,
- "ease", Ease.Cubic.easeIn,
- "delay", delay,
- "alpha", finalAlpha,
- "onUpdate", mUpdateListener,
- "onComplete", finishListener));
- mGlowAnimations.start();
- }
-
- private void hideGlow(int duration, int delay, float finalAlpha,
- AnimatorListener finishListener) {
- mGlowAnimations.cancel();
- mGlowAnimations.add(Tweener.to(mPointCloud.glowManager, duration,
- "ease", Ease.Quart.easeOut,
- "delay", delay,
- "alpha", finalAlpha,
- "x", 0.0f,
- "y", 0.0f,
- "onUpdate", mUpdateListener,
- "onComplete", finishListener));
- mGlowAnimations.start();
- }
-
- private void deactivateTargets() {
- final int count = mTargetDrawables.size();
- for (int i = 0; i < count; i++) {
- TargetDrawable target = mTargetDrawables.get(i);
- target.setState(TargetDrawable.STATE_INACTIVE);
- }
- mActiveTarget = -1;
- }
-
- /**
- * Dispatches a trigger event to listener. Ignored if a listener is not set.
- * @param whichTarget the target that was triggered.
- */
- private void dispatchTriggerEvent(int whichTarget) {
- vibrate();
- if (mOnTriggerListener != null) {
- mOnTriggerListener.onTrigger(this, whichTarget);
- }
- }
-
- private void dispatchOnFinishFinalAnimation() {
- if (mOnTriggerListener != null) {
- mOnTriggerListener.onFinishFinalAnimation();
- }
- }
-
- private void doFinish() {
- final int activeTarget = mActiveTarget;
- final boolean targetHit = activeTarget != -1;
-
- if (targetHit) {
- if (DEBUG) Log.v(TAG, "Finish with target hit = " + targetHit);
-
- highlightSelected(activeTarget);
-
- // Inform listener of any active targets. Typically only one will be active.
- hideGlow(RETURN_TO_HOME_DURATION, RETURN_TO_HOME_DELAY, 0.0f, mResetListener);
- dispatchTriggerEvent(activeTarget);
- if (!mAlwaysTrackFinger) {
- // Force ring and targets to finish animation to final expanded state
- mTargetAnimations.stop();
- }
- } else {
- // Animate handle back to the center based on current state.
- hideGlow(HIDE_ANIMATION_DURATION, 0, 0.0f, mResetListenerWithPing);
- hideTargets(true, false);
- }
-
- setGrabbedState(OnTriggerListener.NO_HANDLE);
- }
-
- private void highlightSelected(int activeTarget) {
- // Highlight the given target and fade others
- mTargetDrawables.get(activeTarget).setState(TargetDrawable.STATE_ACTIVE);
- hideUnselected(activeTarget);
- }
-
- private void hideUnselected(int active) {
- for (int i = 0; i < mTargetDrawables.size(); i++) {
- if (i != active) {
- mTargetDrawables.get(i).setAlpha(0.0f);
- }
- }
- }
-
- private void hideTargets(boolean animate, boolean expanded) {
- mTargetAnimations.cancel();
- // Note: these animations should complete at the same time so that we can swap out
- // the target assets asynchronously from the setTargetResources() call.
- mAnimatingTargets = animate;
- final int duration = animate ? HIDE_ANIMATION_DURATION : 0;
- final int delay = animate ? HIDE_ANIMATION_DELAY : 0;
-
- final float targetScale = expanded ?
- TARGET_SCALE_EXPANDED : TARGET_SCALE_COLLAPSED;
- final int length = mTargetDrawables.size();
- final TimeInterpolator interpolator = Ease.Cubic.easeOut;
- for (int i = 0; i < length; i++) {
- TargetDrawable target = mTargetDrawables.get(i);
- target.setState(TargetDrawable.STATE_INACTIVE);
- mTargetAnimations.add(Tweener.to(target, duration,
- "ease", interpolator,
- "alpha", 0.0f,
- "scaleX", targetScale,
- "scaleY", targetScale,
- "delay", delay,
- "onUpdate", mUpdateListener));
- }
-
- float ringScaleTarget = expanded ?
- RING_SCALE_EXPANDED : RING_SCALE_COLLAPSED;
- ringScaleTarget *= mRingScaleFactor;
- mTargetAnimations.add(Tweener.to(mOuterRing, duration,
- "ease", interpolator,
- "alpha", 0.0f,
- "scaleX", ringScaleTarget,
- "scaleY", ringScaleTarget,
- "delay", delay,
- "onUpdate", mUpdateListener,
- "onComplete", mTargetUpdateListener));
-
- mTargetAnimations.start();
- }
-
- private void showTargets(boolean animate) {
- mTargetAnimations.stop();
- mAnimatingTargets = animate;
- final int delay = animate ? SHOW_ANIMATION_DELAY : 0;
- final int duration = animate ? SHOW_ANIMATION_DURATION : 0;
- final int length = mTargetDrawables.size();
- for (int i = 0; i < length; i++) {
- TargetDrawable target = mTargetDrawables.get(i);
- target.setState(TargetDrawable.STATE_INACTIVE);
- mTargetAnimations.add(Tweener.to(target, duration,
- "ease", Ease.Cubic.easeOut,
- "alpha", 1.0f,
- "scaleX", 1.0f,
- "scaleY", 1.0f,
- "delay", delay,
- "onUpdate", mUpdateListener));
- }
-
- float ringScale = mRingScaleFactor * RING_SCALE_EXPANDED;
- mTargetAnimations.add(Tweener.to(mOuterRing, duration,
- "ease", Ease.Cubic.easeOut,
- "alpha", 1.0f,
- "scaleX", ringScale,
- "scaleY", ringScale,
- "delay", delay,
- "onUpdate", mUpdateListener,
- "onComplete", mTargetUpdateListener));
-
- mTargetAnimations.start();
- }
-
- private void vibrate() {
- final boolean hapticEnabled = Settings.System.getIntForUser(
- mContext.getContentResolver(), Settings.System.HAPTIC_FEEDBACK_ENABLED, 1,
- UserHandle.USER_CURRENT) != 0;
- if (mVibrator != null && hapticEnabled) {
- mVibrator.vibrate(mVibrationDuration, VIBRATION_ATTRIBUTES);
- }
- }
-
- private ArrayList<TargetDrawable> loadDrawableArray(int resourceId) {
- Resources res = getContext().getResources();
- TypedArray array = res.obtainTypedArray(resourceId);
- final int count = array.length();
- ArrayList<TargetDrawable> drawables = new ArrayList<TargetDrawable>(count);
- for (int i = 0; i < count; i++) {
- TypedValue value = array.peekValue(i);
- TargetDrawable target = new TargetDrawable(res, value != null ? value.resourceId : 0);
- drawables.add(target);
- }
- array.recycle();
- return drawables;
- }
-
- private void internalSetTargetResources(int resourceId) {
- final ArrayList<TargetDrawable> targets = loadDrawableArray(resourceId);
- mTargetDrawables = targets;
- mTargetResourceId = resourceId;
-
- int maxWidth = mHandleDrawable.getWidth();
- int maxHeight = mHandleDrawable.getHeight();
- final int count = targets.size();
- for (int i = 0; i < count; i++) {
- TargetDrawable target = targets.get(i);
- maxWidth = Math.max(maxWidth, target.getWidth());
- maxHeight = Math.max(maxHeight, target.getHeight());
- }
- if (mMaxTargetWidth != maxWidth || mMaxTargetHeight != maxHeight) {
- mMaxTargetWidth = maxWidth;
- mMaxTargetHeight = maxHeight;
- requestLayout(); // required to resize layout and call updateTargetPositions()
- } else {
- updateTargetPositions(mWaveCenterX, mWaveCenterY);
- updatePointCloudPosition(mWaveCenterX, mWaveCenterY);
- }
- }
-
- /**
- * Loads an array of drawables from the given resourceId.
- *
- * @param resourceId
- */
- public void setTargetResources(int resourceId) {
- if (mAnimatingTargets) {
- // postpone this change until we return to the initial state
- mNewTargetResources = resourceId;
- } else {
- internalSetTargetResources(resourceId);
- }
- }
-
- public int getTargetResourceId() {
- return mTargetResourceId;
- }
-
- /**
- * Sets the resource id specifying the target descriptions for accessibility.
- *
- * @param resourceId The resource id.
- */
- public void setTargetDescriptionsResourceId(int resourceId) {
- mTargetDescriptionsResourceId = resourceId;
- if (mTargetDescriptions != null) {
- mTargetDescriptions.clear();
- }
- }
-
- /**
- * Gets the resource id specifying the target descriptions for accessibility.
- *
- * @return The resource id.
- */
- public int getTargetDescriptionsResourceId() {
- return mTargetDescriptionsResourceId;
- }
-
- /**
- * Sets the resource id specifying the target direction descriptions for accessibility.
- *
- * @param resourceId The resource id.
- */
- public void setDirectionDescriptionsResourceId(int resourceId) {
- mDirectionDescriptionsResourceId = resourceId;
- if (mDirectionDescriptions != null) {
- mDirectionDescriptions.clear();
- }
- }
-
- /**
- * Gets the resource id specifying the target direction descriptions.
- *
- * @return The resource id.
- */
- public int getDirectionDescriptionsResourceId() {
- return mDirectionDescriptionsResourceId;
- }
-
- /**
- * Enable or disable vibrate on touch.
- *
- * @param enabled
- */
- public void setVibrateEnabled(boolean enabled) {
- if (enabled && mVibrator == null) {
- mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE);
- } else {
- mVibrator = null;
- }
- }
-
- /**
- * Starts wave animation.
- *
- */
- public void ping() {
- if (mFeedbackCount > 0) {
- boolean doWaveAnimation = true;
- final AnimationBundle waveAnimations = mWaveAnimations;
-
- // Don't do a wave if there's already one in progress
- if (waveAnimations.size() > 0 && waveAnimations.get(0).animator.isRunning()) {
- long t = waveAnimations.get(0).animator.getCurrentPlayTime();
- if (t < WAVE_ANIMATION_DURATION/2) {
- doWaveAnimation = false;
- }
- }
-
- if (doWaveAnimation) {
- startWaveAnimation();
- }
- }
- }
-
- private void stopAndHideWaveAnimation() {
- mWaveAnimations.cancel();
- mPointCloud.waveManager.setAlpha(0.0f);
- }
-
- private void startWaveAnimation() {
- mWaveAnimations.cancel();
- mPointCloud.waveManager.setAlpha(1.0f);
- mPointCloud.waveManager.setRadius(mHandleDrawable.getWidth()/2.0f);
- mWaveAnimations.add(Tweener.to(mPointCloud.waveManager, WAVE_ANIMATION_DURATION,
- "ease", Ease.Quad.easeOut,
- "delay", 0,
- "radius", 2.0f * mOuterRadius,
- "onUpdate", mUpdateListener,
- "onComplete",
- new AnimatorListenerAdapter() {
- public void onAnimationEnd(Animator animator) {
- mPointCloud.waveManager.setRadius(0.0f);
- mPointCloud.waveManager.setAlpha(0.0f);
- }
- }));
- mWaveAnimations.start();
- }
-
- /**
- * Resets the widget to default state and cancels all animation. If animate is 'true', will
- * animate objects into place. Otherwise, objects will snap back to place.
- *
- * @param animate
- */
- public void reset(boolean animate) {
- mGlowAnimations.stop();
- mTargetAnimations.stop();
- startBackgroundAnimation(0, 0.0f);
- stopAndHideWaveAnimation();
- hideTargets(animate, false);
- hideGlow(0, 0, 0.0f, null);
- Tweener.reset();
- }
-
- private void startBackgroundAnimation(int duration, float alpha) {
- final Drawable background = getBackground();
- if (mAlwaysTrackFinger && background != null) {
- if (mBackgroundAnimator != null) {
- mBackgroundAnimator.animator.cancel();
- }
- mBackgroundAnimator = Tweener.to(background, duration,
- "ease", Ease.Cubic.easeIn,
- "alpha", (int)(255.0f * alpha),
- "delay", SHOW_ANIMATION_DELAY);
- mBackgroundAnimator.animator.start();
- }
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- final int action = event.getActionMasked();
- boolean handled = false;
- switch (action) {
- case MotionEvent.ACTION_POINTER_DOWN:
- case MotionEvent.ACTION_DOWN:
- if (DEBUG) Log.v(TAG, "*** DOWN ***");
- handleDown(event);
- handleMove(event);
- handled = true;
- break;
-
- case MotionEvent.ACTION_MOVE:
- if (DEBUG) Log.v(TAG, "*** MOVE ***");
- handleMove(event);
- handled = true;
- break;
-
- case MotionEvent.ACTION_POINTER_UP:
- case MotionEvent.ACTION_UP:
- if (DEBUG) Log.v(TAG, "*** UP ***");
- handleMove(event);
- handleUp(event);
- handled = true;
- break;
-
- case MotionEvent.ACTION_CANCEL:
- if (DEBUG) Log.v(TAG, "*** CANCEL ***");
- handleMove(event);
- handleCancel(event);
- handled = true;
- break;
-
- }
- invalidate();
- return handled ? true : super.onTouchEvent(event);
- }
-
- private void updateGlowPosition(float x, float y) {
- float dx = x - mOuterRing.getX();
- float dy = y - mOuterRing.getY();
- dx *= 1f / mRingScaleFactor;
- dy *= 1f / mRingScaleFactor;
- mPointCloud.glowManager.setX(mOuterRing.getX() + dx);
- mPointCloud.glowManager.setY(mOuterRing.getY() + dy);
- }
-
- private void handleDown(MotionEvent event) {
- int actionIndex = event.getActionIndex();
- float eventX = event.getX(actionIndex);
- float eventY = event.getY(actionIndex);
- switchToState(STATE_START, eventX, eventY);
- if (!trySwitchToFirstTouchState(eventX, eventY)) {
- mDragging = false;
- } else {
- mPointerId = event.getPointerId(actionIndex);
- updateGlowPosition(eventX, eventY);
- }
- }
-
- private void handleUp(MotionEvent event) {
- if (DEBUG && mDragging) Log.v(TAG, "** Handle RELEASE");
- int actionIndex = event.getActionIndex();
- if (event.getPointerId(actionIndex) == mPointerId) {
- switchToState(STATE_FINISH, event.getX(actionIndex), event.getY(actionIndex));
- }
- }
-
- private void handleCancel(MotionEvent event) {
- if (DEBUG && mDragging) Log.v(TAG, "** Handle CANCEL");
-
- // Drop the active target if canceled.
- mActiveTarget = -1;
-
- int actionIndex = event.findPointerIndex(mPointerId);
- actionIndex = actionIndex == -1 ? 0 : actionIndex;
- switchToState(STATE_FINISH, event.getX(actionIndex), event.getY(actionIndex));
- }
-
- private void handleMove(MotionEvent event) {
- int activeTarget = -1;
- final int historySize = event.getHistorySize();
- ArrayList<TargetDrawable> targets = mTargetDrawables;
- int ntargets = targets.size();
- float x = 0.0f;
- float y = 0.0f;
- float activeAngle = 0.0f;
- int actionIndex = event.findPointerIndex(mPointerId);
-
- if (actionIndex == -1) {
- return; // no data for this pointer
- }
-
- for (int k = 0; k < historySize + 1; k++) {
- float eventX = k < historySize ? event.getHistoricalX(actionIndex, k)
- : event.getX(actionIndex);
- float eventY = k < historySize ? event.getHistoricalY(actionIndex, k)
- : event.getY(actionIndex);
- // tx and ty are relative to wave center
- float tx = eventX - mWaveCenterX;
- float ty = eventY - mWaveCenterY;
- float touchRadius = (float) Math.hypot(tx, ty);
- final float scale = touchRadius > mOuterRadius ? mOuterRadius / touchRadius : 1.0f;
- float limitX = tx * scale;
- float limitY = ty * scale;
- double angleRad = Math.atan2(-ty, tx);
-
- if (!mDragging) {
- trySwitchToFirstTouchState(eventX, eventY);
- }
-
- if (mDragging) {
- // For multiple targets, snap to the one that matches
- final float snapRadius = mRingScaleFactor * mOuterRadius - mSnapMargin;
- final float snapDistance2 = snapRadius * snapRadius;
- // Find first target in range
- for (int i = 0; i < ntargets; i++) {
- TargetDrawable target = targets.get(i);
-
- double targetMinRad = mFirstItemOffset + (i - 0.5) * 2 * Math.PI / ntargets;
- double targetMaxRad = mFirstItemOffset + (i + 0.5) * 2 * Math.PI / ntargets;
- if (target.isEnabled()) {
- boolean angleMatches =
- (angleRad > targetMinRad && angleRad <= targetMaxRad) ||
- (angleRad + 2 * Math.PI > targetMinRad &&
- angleRad + 2 * Math.PI <= targetMaxRad) ||
- (angleRad - 2 * Math.PI > targetMinRad &&
- angleRad - 2 * Math.PI <= targetMaxRad);
- if (angleMatches && (dist2(tx, ty) > snapDistance2)) {
- activeTarget = i;
- activeAngle = (float) -angleRad;
- }
- }
- }
- }
- x = limitX;
- y = limitY;
- }
-
- if (!mDragging) {
- return;
- }
-
- if (activeTarget != -1) {
- switchToState(STATE_SNAP, x,y);
- updateGlowPosition(x, y);
- } else {
- switchToState(STATE_TRACKING, x, y);
- updateGlowPosition(x, y);
- }
-
- if (mActiveTarget != activeTarget) {
- // Defocus the old target
- if (mActiveTarget != -1) {
- TargetDrawable target = targets.get(mActiveTarget);
- if (target.hasState(TargetDrawable.STATE_FOCUSED)) {
- target.setState(TargetDrawable.STATE_INACTIVE);
- }
- if (mMagneticTargets) {
- updateTargetPosition(mActiveTarget, mWaveCenterX, mWaveCenterY);
- }
- }
- // Focus the new target
- if (activeTarget != -1) {
- TargetDrawable target = targets.get(activeTarget);
- if (target.hasState(TargetDrawable.STATE_FOCUSED)) {
- target.setState(TargetDrawable.STATE_FOCUSED);
- }
- if (mMagneticTargets) {
- updateTargetPosition(activeTarget, mWaveCenterX, mWaveCenterY, activeAngle);
- }
- if (AccessibilityManager.getInstance(mContext).isEnabled()) {
- String targetContentDescription = getTargetDescription(activeTarget);
- announceForAccessibility(targetContentDescription);
- }
- }
- }
- mActiveTarget = activeTarget;
- }
-
- @Override
- public boolean onHoverEvent(MotionEvent event) {
- if (AccessibilityManager.getInstance(mContext).isTouchExplorationEnabled()) {
- final int action = event.getAction();
- switch (action) {
- case MotionEvent.ACTION_HOVER_ENTER:
- event.setAction(MotionEvent.ACTION_DOWN);
- break;
- case MotionEvent.ACTION_HOVER_MOVE:
- event.setAction(MotionEvent.ACTION_MOVE);
- break;
- case MotionEvent.ACTION_HOVER_EXIT:
- event.setAction(MotionEvent.ACTION_UP);
- break;
- }
- onTouchEvent(event);
- event.setAction(action);
- }
- super.onHoverEvent(event);
- return true;
- }
-
- /**
- * Sets the current grabbed state, and dispatches a grabbed state change
- * event to our listener.
- */
- private void setGrabbedState(int newState) {
- if (newState != mGrabbedState) {
- if (newState != OnTriggerListener.NO_HANDLE) {
- vibrate();
- }
- mGrabbedState = newState;
- if (mOnTriggerListener != null) {
- if (newState == OnTriggerListener.NO_HANDLE) {
- mOnTriggerListener.onReleased(this, OnTriggerListener.CENTER_HANDLE);
- } else {
- mOnTriggerListener.onGrabbed(this, OnTriggerListener.CENTER_HANDLE);
- }
- mOnTriggerListener.onGrabbedStateChange(this, newState);
- }
- }
- }
-
- private boolean trySwitchToFirstTouchState(float x, float y) {
- final float tx = x - mWaveCenterX;
- final float ty = y - mWaveCenterY;
- if (mAlwaysTrackFinger || dist2(tx,ty) <= getScaledGlowRadiusSquared()) {
- if (DEBUG) Log.v(TAG, "** Handle HIT");
- switchToState(STATE_FIRST_TOUCH, x, y);
- updateGlowPosition(tx, ty);
- mDragging = true;
- return true;
- }
- return false;
- }
-
- private void assignDefaultsIfNeeded() {
- if (mOuterRadius == 0.0f) {
- mOuterRadius = Math.max(mOuterRing.getWidth(), mOuterRing.getHeight())/2.0f;
- }
- if (mSnapMargin == 0.0f) {
- mSnapMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- SNAP_MARGIN_DEFAULT, getContext().getResources().getDisplayMetrics());
- }
- if (mInnerRadius == 0.0f) {
- mInnerRadius = mHandleDrawable.getWidth() / 10.0f;
- }
- }
-
- private void computeInsets(int dx, int dy) {
- final int layoutDirection = getLayoutDirection();
- final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
-
- switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
- case Gravity.LEFT:
- mHorizontalInset = 0;
- break;
- case Gravity.RIGHT:
- mHorizontalInset = dx;
- break;
- case Gravity.CENTER_HORIZONTAL:
- default:
- mHorizontalInset = dx / 2;
- break;
- }
- switch (absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK) {
- case Gravity.TOP:
- mVerticalInset = 0;
- break;
- case Gravity.BOTTOM:
- mVerticalInset = dy;
- break;
- case Gravity.CENTER_VERTICAL:
- default:
- mVerticalInset = dy / 2;
- break;
- }
- }
-
- /**
- * Given the desired width and height of the ring and the allocated width and height, compute
- * how much we need to scale the ring.
- */
- private float computeScaleFactor(int desiredWidth, int desiredHeight,
- int actualWidth, int actualHeight) {
-
- // Return unity if scaling is not allowed.
- if (!mAllowScaling) return 1f;
-
- final int layoutDirection = getLayoutDirection();
- final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
-
- float scaleX = 1f;
- float scaleY = 1f;
-
- // We use the gravity as a cue for whether we want to scale on a particular axis.
- // We only scale to fit horizontally if we're not pinned to the left or right. Likewise,
- // we only scale to fit vertically if we're not pinned to the top or bottom. In these
- // cases, we want the ring to hang off the side or top/bottom, respectively.
- switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
- case Gravity.LEFT:
- case Gravity.RIGHT:
- break;
- case Gravity.CENTER_HORIZONTAL:
- default:
- if (desiredWidth > actualWidth) {
- scaleX = (1f * actualWidth - mMaxTargetWidth) /
- (desiredWidth - mMaxTargetWidth);
- }
- break;
- }
- switch (absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK) {
- case Gravity.TOP:
- case Gravity.BOTTOM:
- break;
- case Gravity.CENTER_VERTICAL:
- default:
- if (desiredHeight > actualHeight) {
- scaleY = (1f * actualHeight - mMaxTargetHeight) /
- (desiredHeight - mMaxTargetHeight);
- }
- break;
- }
- return Math.min(scaleX, scaleY);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- final int minimumWidth = getSuggestedMinimumWidth();
- final int minimumHeight = getSuggestedMinimumHeight();
- int computedWidth = resolveMeasured(widthMeasureSpec, minimumWidth);
- int computedHeight = resolveMeasured(heightMeasureSpec, minimumHeight);
-
- mRingScaleFactor = computeScaleFactor(minimumWidth, minimumHeight,
- computedWidth, computedHeight);
-
- int scaledWidth = getScaledSuggestedMinimumWidth();
- int scaledHeight = getScaledSuggestedMinimumHeight();
-
- computeInsets(computedWidth - scaledWidth, computedHeight - scaledHeight);
- setMeasuredDimension(computedWidth, computedHeight);
- }
-
- private float getRingWidth() {
- return mRingScaleFactor * Math.max(mOuterRing.getWidth(), 2 * mOuterRadius);
- }
-
- private float getRingHeight() {
- return mRingScaleFactor * Math.max(mOuterRing.getHeight(), 2 * mOuterRadius);
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- final int width = right - left;
- final int height = bottom - top;
-
- // Target placement width/height. This puts the targets on the greater of the ring
- // width or the specified outer radius.
- final float placementWidth = getRingWidth();
- final float placementHeight = getRingHeight();
- float newWaveCenterX = mHorizontalInset
- + Math.max(width, mMaxTargetWidth + placementWidth) / 2;
- float newWaveCenterY = mVerticalInset
- + Math.max(height, + mMaxTargetHeight + placementHeight) / 2;
-
- if (mInitialLayout) {
- stopAndHideWaveAnimation();
- hideTargets(false, false);
- mInitialLayout = false;
- }
-
- mOuterRing.setPositionX(newWaveCenterX);
- mOuterRing.setPositionY(newWaveCenterY);
-
- mPointCloud.setScale(mRingScaleFactor);
-
- mHandleDrawable.setPositionX(newWaveCenterX);
- mHandleDrawable.setPositionY(newWaveCenterY);
-
- updateTargetPositions(newWaveCenterX, newWaveCenterY);
- updatePointCloudPosition(newWaveCenterX, newWaveCenterY);
- updateGlowPosition(newWaveCenterX, newWaveCenterY);
-
- mWaveCenterX = newWaveCenterX;
- mWaveCenterY = newWaveCenterY;
-
- if (DEBUG) dump();
- }
-
- private void updateTargetPosition(int i, float centerX, float centerY) {
- final float angle = getAngle(getSliceAngle(), i);
- updateTargetPosition(i, centerX, centerY, angle);
- }
-
- private void updateTargetPosition(int i, float centerX, float centerY, float angle) {
- final float placementRadiusX = getRingWidth() / 2;
- final float placementRadiusY = getRingHeight() / 2;
- if (i >= 0) {
- ArrayList<TargetDrawable> targets = mTargetDrawables;
- final TargetDrawable targetIcon = targets.get(i);
- targetIcon.setPositionX(centerX);
- targetIcon.setPositionY(centerY);
- targetIcon.setX(placementRadiusX * (float) Math.cos(angle));
- targetIcon.setY(placementRadiusY * (float) Math.sin(angle));
- }
- }
-
- private void updateTargetPositions(float centerX, float centerY) {
- updateTargetPositions(centerX, centerY, false);
- }
-
- private void updateTargetPositions(float centerX, float centerY, boolean skipActive) {
- final int size = mTargetDrawables.size();
- final float alpha = getSliceAngle();
- // Reposition the target drawables if the view changed.
- for (int i = 0; i < size; i++) {
- if (!skipActive || i != mActiveTarget) {
- updateTargetPosition(i, centerX, centerY, getAngle(alpha, i));
- }
- }
- }
-
- private float getAngle(float alpha, int i) {
- return mFirstItemOffset + alpha * i;
- }
-
- private float getSliceAngle() {
- return (float) (-2.0f * Math.PI / mTargetDrawables.size());
- }
-
- private void updatePointCloudPosition(float centerX, float centerY) {
- mPointCloud.setCenter(centerX, centerY);
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- mPointCloud.draw(canvas);
- mOuterRing.draw(canvas);
- final int ntargets = mTargetDrawables.size();
- for (int i = 0; i < ntargets; i++) {
- TargetDrawable target = mTargetDrawables.get(i);
- if (target != null) {
- target.draw(canvas);
- }
- }
- mHandleDrawable.draw(canvas);
- }
-
- public void setOnTriggerListener(OnTriggerListener listener) {
- mOnTriggerListener = listener;
- }
-
- private float square(float d) {
- return d * d;
- }
-
- private float dist2(float dx, float dy) {
- return dx*dx + dy*dy;
- }
-
- private float getScaledGlowRadiusSquared() {
- final float scaledTapRadius;
- if (AccessibilityManager.getInstance(mContext).isEnabled()) {
- scaledTapRadius = TAP_RADIUS_SCALE_ACCESSIBILITY_ENABLED * mGlowRadius;
- } else {
- scaledTapRadius = mGlowRadius;
- }
- return square(scaledTapRadius);
- }
-
- private void announceTargets() {
- StringBuilder utterance = new StringBuilder();
- final int targetCount = mTargetDrawables.size();
- for (int i = 0; i < targetCount; i++) {
- String targetDescription = getTargetDescription(i);
- String directionDescription = getDirectionDescription(i);
- if (!TextUtils.isEmpty(targetDescription)
- && !TextUtils.isEmpty(directionDescription)) {
- String text = String.format(directionDescription, targetDescription);
- utterance.append(text);
- }
- }
- if (utterance.length() > 0) {
- announceForAccessibility(utterance.toString());
- }
- }
-
- private String getTargetDescription(int index) {
- if (mTargetDescriptions == null || mTargetDescriptions.isEmpty()) {
- mTargetDescriptions = loadDescriptions(mTargetDescriptionsResourceId);
- if (mTargetDrawables.size() != mTargetDescriptions.size()) {
- Log.w(TAG, "The number of target drawables must be"
- + " equal to the number of target descriptions.");
- return null;
- }
- }
- return mTargetDescriptions.get(index);
- }
-
- private String getDirectionDescription(int index) {
- if (mDirectionDescriptions == null || mDirectionDescriptions.isEmpty()) {
- mDirectionDescriptions = loadDescriptions(mDirectionDescriptionsResourceId);
- if (mTargetDrawables.size() != mDirectionDescriptions.size()) {
- Log.w(TAG, "The number of target drawables must be"
- + " equal to the number of direction descriptions.");
- return null;
- }
- }
- return mDirectionDescriptions.get(index);
- }
-
- private ArrayList<String> loadDescriptions(int resourceId) {
- TypedArray array = getContext().getResources().obtainTypedArray(resourceId);
- final int count = array.length();
- ArrayList<String> targetContentDescriptions = new ArrayList<String>(count);
- for (int i = 0; i < count; i++) {
- String contentDescription = array.getString(i);
- targetContentDescriptions.add(contentDescription);
- }
- array.recycle();
- return targetContentDescriptions;
- }
-
- public int getResourceIdForTarget(int index) {
- final TargetDrawable drawable = mTargetDrawables.get(index);
- return drawable == null ? 0 : drawable.getResourceId();
- }
-
- public void setEnableTarget(int resourceId, boolean enabled) {
- for (int i = 0; i < mTargetDrawables.size(); i++) {
- final TargetDrawable target = mTargetDrawables.get(i);
- if (target.getResourceId() == resourceId) {
- target.setEnabled(enabled);
- break; // should never be more than one match
- }
- }
- }
-
- /**
- * Gets the position of a target in the array that matches the given resource.
- * @param resourceId
- * @return the index or -1 if not found
- */
- public int getTargetPosition(int resourceId) {
- for (int i = 0; i < mTargetDrawables.size(); i++) {
- final TargetDrawable target = mTargetDrawables.get(i);
- if (target.getResourceId() == resourceId) {
- return i; // should never be more than one match
- }
- }
- return -1;
- }
-
- private boolean replaceTargetDrawables(Resources res, int existingResourceId,
- int newResourceId) {
- if (existingResourceId == 0 || newResourceId == 0) {
- return false;
- }
-
- boolean result = false;
- final ArrayList<TargetDrawable> drawables = mTargetDrawables;
- final int size = drawables.size();
- for (int i = 0; i < size; i++) {
- final TargetDrawable target = drawables.get(i);
- if (target != null && target.getResourceId() == existingResourceId) {
- target.setDrawable(res, newResourceId);
- result = true;
- }
- }
-
- if (result) {
- requestLayout(); // in case any given drawable's size changes
- }
-
- return result;
- }
-
- /**
- * Searches the given package for a resource to use to replace the Drawable on the
- * target with the given resource id
- * @param component of the .apk that contains the resource
- * @param name of the metadata in the .apk
- * @param existingResId the resource id of the target to search for
- * @return true if found in the given package and replaced at least one target Drawables
- */
- public boolean replaceTargetDrawablesIfPresent(ComponentName component, String name,
- int existingResId) {
- if (existingResId == 0) return false;
-
- boolean replaced = false;
- if (component != null) {
- try {
- PackageManager packageManager = mContext.getPackageManager();
- // Look for the search icon specified in the activity meta-data
- Bundle metaData = packageManager.getActivityInfo(
- component, PackageManager.GET_META_DATA).metaData;
- if (metaData != null) {
- int iconResId = metaData.getInt(name);
- if (iconResId != 0) {
- Resources res = packageManager.getResourcesForActivity(component);
- replaced = replaceTargetDrawables(res, existingResId, iconResId);
- }
- }
- } catch (NameNotFoundException e) {
- Log.w(TAG, "Failed to swap drawable; "
- + component.flattenToShortString() + " not found", e);
- } catch (Resources.NotFoundException nfe) {
- Log.w(TAG, "Failed to swap drawable from "
- + component.flattenToShortString(), nfe);
- }
- }
- if (!replaced) {
- // Restore the original drawable
- replaceTargetDrawables(mContext.getResources(), existingResId, existingResId);
- }
- return replaced;
- }
-}
diff --git a/core/java/com/android/internal/widget/multiwaveview/PointCloud.java b/core/java/com/android/internal/widget/multiwaveview/PointCloud.java
deleted file mode 100644
index 6f26b99..0000000
--- a/core/java/com/android/internal/widget/multiwaveview/PointCloud.java
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * Copyright (C) 2012 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 com.android.internal.widget.multiwaveview;
-
-import java.util.ArrayList;
-
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.drawable.Drawable;
-import android.util.Log;
-
-public class PointCloud {
- private static final float MIN_POINT_SIZE = 2.0f;
- private static final float MAX_POINT_SIZE = 4.0f;
- private static final int INNER_POINTS = 8;
- private static final String TAG = "PointCloud";
- private ArrayList<Point> mPointCloud = new ArrayList<Point>();
- private Drawable mDrawable;
- private float mCenterX;
- private float mCenterY;
- private Paint mPaint;
- private float mScale = 1.0f;
- private static final float PI = (float) Math.PI;
-
- // These allow us to have multiple concurrent animations.
- WaveManager waveManager = new WaveManager();
- GlowManager glowManager = new GlowManager();
- private float mOuterRadius;
-
- public class WaveManager {
- private float radius = 50;
- private float alpha = 0.0f;
-
- public void setRadius(float r) {
- radius = r;
- }
-
- public float getRadius() {
- return radius;
- }
-
- public void setAlpha(float a) {
- alpha = a;
- }
-
- public float getAlpha() {
- return alpha;
- }
- };
-
- public class GlowManager {
- private float x;
- private float y;
- private float radius = 0.0f;
- private float alpha = 0.0f;
-
- public void setX(float x1) {
- x = x1;
- }
-
- public float getX() {
- return x;
- }
-
- public void setY(float y1) {
- y = y1;
- }
-
- public float getY() {
- return y;
- }
-
- public void setAlpha(float a) {
- alpha = a;
- }
-
- public float getAlpha() {
- return alpha;
- }
-
- public void setRadius(float r) {
- radius = r;
- }
-
- public float getRadius() {
- return radius;
- }
- }
-
- class Point {
- float x;
- float y;
- float radius;
-
- public Point(float x2, float y2, float r) {
- x = (float) x2;
- y = (float) y2;
- radius = r;
- }
- }
-
- public PointCloud(Drawable drawable) {
- mPaint = new Paint();
- mPaint.setFilterBitmap(true);
- mPaint.setColor(Color.rgb(255, 255, 255)); // TODO: make configurable
- mPaint.setAntiAlias(true);
- mPaint.setDither(true);
-
- mDrawable = drawable;
- if (mDrawable != null) {
- drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
- }
- }
-
- public void setCenter(float x, float y) {
- mCenterX = x;
- mCenterY = y;
- }
-
- public void makePointCloud(float innerRadius, float outerRadius) {
- if (innerRadius == 0) {
- Log.w(TAG, "Must specify an inner radius");
- return;
- }
- mOuterRadius = outerRadius;
- mPointCloud.clear();
- final float pointAreaRadius = (outerRadius - innerRadius);
- final float ds = (2.0f * PI * innerRadius / INNER_POINTS);
- final int bands = (int) Math.round(pointAreaRadius / ds);
- final float dr = pointAreaRadius / bands;
- float r = innerRadius;
- for (int b = 0; b <= bands; b++, r += dr) {
- float circumference = 2.0f * PI * r;
- final int pointsInBand = (int) (circumference / ds);
- float eta = PI/2.0f;
- float dEta = 2.0f * PI / pointsInBand;
- for (int i = 0; i < pointsInBand; i++) {
- float x = r * (float) Math.cos(eta);
- float y = r * (float) Math.sin(eta);
- eta += dEta;
- mPointCloud.add(new Point(x, y, r));
- }
- }
- }
-
- public void setScale(float scale) {
- mScale = scale;
- }
-
- public float getScale() {
- return mScale;
- }
-
- public int getAlphaForPoint(Point point) {
- // Contribution from positional glow
- float glowDistance = (float) Math.hypot(glowManager.x - point.x, glowManager.y - point.y);
- float glowAlpha = 0.0f;
- if (glowDistance < glowManager.radius) {
- float cosf = (float) Math.cos(PI * 0.25f * glowDistance / glowManager.radius);
- glowAlpha = glowManager.alpha * Math.max(0.0f, (float) Math.pow(cosf, 10.0f));
- }
-
- // Compute contribution from Wave
- float radius = (float) Math.hypot(point.x, point.y);
- float waveAlpha = 0.0f;
- if (radius < waveManager.radius * 2) {
- float distanceToWaveRing = (radius - waveManager.radius);
- float cosf = (float) Math.cos(PI * 0.5f * distanceToWaveRing / waveManager.radius);
- waveAlpha = waveManager.alpha * Math.max(0.0f, (float) Math.pow(cosf, 6.0f));
- }
- return (int) (Math.max(glowAlpha, waveAlpha) * 255);
- }
-
- private float interp(float min, float max, float f) {
- return min + (max - min) * f;
- }
-
- public void draw(Canvas canvas) {
- ArrayList<Point> points = mPointCloud;
- canvas.save(Canvas.MATRIX_SAVE_FLAG);
- canvas.scale(mScale, mScale, mCenterX, mCenterY);
- for (int i = 0; i < points.size(); i++) {
- Point point = points.get(i);
- final float pointSize = interp(MAX_POINT_SIZE, MIN_POINT_SIZE,
- point.radius / mOuterRadius);
- final float px = point.x + mCenterX;
- final float py = point.y + mCenterY;
- int alpha = getAlphaForPoint(point);
-
- if (alpha == 0) continue;
-
- if (mDrawable != null) {
- canvas.save(Canvas.MATRIX_SAVE_FLAG);
- final float cx = mDrawable.getIntrinsicWidth() * 0.5f;
- final float cy = mDrawable.getIntrinsicHeight() * 0.5f;
- final float s = pointSize / MAX_POINT_SIZE;
- canvas.scale(s, s, px, py);
- canvas.translate(px - cx, py - cy);
- mDrawable.setAlpha(alpha);
- mDrawable.draw(canvas);
- canvas.restore();
- } else {
- mPaint.setAlpha(alpha);
- canvas.drawCircle(px, py, pointSize, mPaint);
- }
- }
- canvas.restore();
- }
-
-}
diff --git a/core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java b/core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java
deleted file mode 100644
index 5a4c441..0000000
--- a/core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.widget.multiwaveview;
-
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.StateListDrawable;
-import android.util.Log;
-
-public class TargetDrawable {
- private static final String TAG = "TargetDrawable";
- private static final boolean DEBUG = false;
-
- public static final int[] STATE_ACTIVE =
- { android.R.attr.state_enabled, android.R.attr.state_active };
- public static final int[] STATE_INACTIVE =
- { android.R.attr.state_enabled, -android.R.attr.state_active };
- public static final int[] STATE_FOCUSED =
- { android.R.attr.state_enabled, -android.R.attr.state_active,
- android.R.attr.state_focused };
-
- private float mTranslationX = 0.0f;
- private float mTranslationY = 0.0f;
- private float mPositionX = 0.0f;
- private float mPositionY = 0.0f;
- private float mScaleX = 1.0f;
- private float mScaleY = 1.0f;
- private float mAlpha = 1.0f;
- private Drawable mDrawable;
- private boolean mEnabled = true;
- private final int mResourceId;
-
- public TargetDrawable(Resources res, int resId) {
- mResourceId = resId;
- setDrawable(res, resId);
- }
-
- public void setDrawable(Resources res, int resId) {
- // Note we explicitly don't set mResourceId to resId since we allow the drawable to be
- // swapped at runtime and want to re-use the existing resource id for identification.
- Drawable drawable = resId == 0 ? null : res.getDrawable(resId);
- // Mutate the drawable so we can animate shared drawable properties.
- mDrawable = drawable != null ? drawable.mutate() : null;
- resizeDrawables();
- setState(STATE_INACTIVE);
- }
-
- public TargetDrawable(TargetDrawable other) {
- mResourceId = other.mResourceId;
- // Mutate the drawable so we can animate shared drawable properties.
- mDrawable = other.mDrawable != null ? other.mDrawable.mutate() : null;
- resizeDrawables();
- setState(STATE_INACTIVE);
- }
-
- public void setState(int [] state) {
- if (mDrawable instanceof StateListDrawable) {
- StateListDrawable d = (StateListDrawable) mDrawable;
- d.setState(state);
- }
- }
-
- public boolean hasState(int [] state) {
- if (mDrawable instanceof StateListDrawable) {
- StateListDrawable d = (StateListDrawable) mDrawable;
- // TODO: this doesn't seem to work
- return d.getStateDrawableIndex(state) != -1;
- }
- return false;
- }
-
- /**
- * Returns true if the drawable is a StateListDrawable and is in the focused state.
- *
- * @return
- */
- public boolean isActive() {
- if (mDrawable instanceof StateListDrawable) {
- StateListDrawable d = (StateListDrawable) mDrawable;
- int[] states = d.getState();
- for (int i = 0; i < states.length; i++) {
- if (states[i] == android.R.attr.state_focused) {
- return true;
- }
- }
- }
- return false;
- }
-
- /**
- * Returns true if this target is enabled. Typically an enabled target contains a valid
- * drawable in a valid state. Currently all targets with valid drawables are valid.
- *
- * @return
- */
- public boolean isEnabled() {
- return mDrawable != null && mEnabled;
- }
-
- /**
- * Makes drawables in a StateListDrawable all the same dimensions.
- * If not a StateListDrawable, then justs sets the bounds to the intrinsic size of the
- * drawable.
- */
- private void resizeDrawables() {
- if (mDrawable instanceof StateListDrawable) {
- StateListDrawable d = (StateListDrawable) mDrawable;
- int maxWidth = 0;
- int maxHeight = 0;
- for (int i = 0; i < d.getStateCount(); i++) {
- Drawable childDrawable = d.getStateDrawable(i);
- maxWidth = Math.max(maxWidth, childDrawable.getIntrinsicWidth());
- maxHeight = Math.max(maxHeight, childDrawable.getIntrinsicHeight());
- }
- if (DEBUG) Log.v(TAG, "union of childDrawable rects " + d + " to: "
- + maxWidth + "x" + maxHeight);
- d.setBounds(0, 0, maxWidth, maxHeight);
- for (int i = 0; i < d.getStateCount(); i++) {
- Drawable childDrawable = d.getStateDrawable(i);
- if (DEBUG) Log.v(TAG, "sizing drawable " + childDrawable + " to: "
- + maxWidth + "x" + maxHeight);
- childDrawable.setBounds(0, 0, maxWidth, maxHeight);
- }
- } else if (mDrawable != null) {
- mDrawable.setBounds(0, 0,
- mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight());
- }
- }
-
- public void setX(float x) {
- mTranslationX = x;
- }
-
- public void setY(float y) {
- mTranslationY = y;
- }
-
- public void setScaleX(float x) {
- mScaleX = x;
- }
-
- public void setScaleY(float y) {
- mScaleY = y;
- }
-
- public void setAlpha(float alpha) {
- mAlpha = alpha;
- }
-
- public float getX() {
- return mTranslationX;
- }
-
- public float getY() {
- return mTranslationY;
- }
-
- public float getScaleX() {
- return mScaleX;
- }
-
- public float getScaleY() {
- return mScaleY;
- }
-
- public float getAlpha() {
- return mAlpha;
- }
-
- public void setPositionX(float x) {
- mPositionX = x;
- }
-
- public void setPositionY(float y) {
- mPositionY = y;
- }
-
- public float getPositionX() {
- return mPositionX;
- }
-
- public float getPositionY() {
- return mPositionY;
- }
-
- public int getWidth() {
- return mDrawable != null ? mDrawable.getIntrinsicWidth() : 0;
- }
-
- public int getHeight() {
- return mDrawable != null ? mDrawable.getIntrinsicHeight() : 0;
- }
-
- public void draw(Canvas canvas) {
- if (mDrawable == null || !mEnabled) {
- return;
- }
- canvas.save(Canvas.MATRIX_SAVE_FLAG);
- canvas.scale(mScaleX, mScaleY, mPositionX, mPositionY);
- canvas.translate(mTranslationX + mPositionX, mTranslationY + mPositionY);
- canvas.translate(-0.5f * getWidth(), -0.5f * getHeight());
- mDrawable.setAlpha((int) Math.round(mAlpha * 255f));
- mDrawable.draw(canvas);
- canvas.restore();
- }
-
- public void setEnabled(boolean enabled) {
- mEnabled = enabled;
- }
-
- public int getResourceId() {
- return mResourceId;
- }
-}
diff --git a/core/java/com/android/internal/widget/multiwaveview/Tweener.java b/core/java/com/android/internal/widget/multiwaveview/Tweener.java
deleted file mode 100644
index d559d9d..0000000
--- a/core/java/com/android/internal/widget/multiwaveview/Tweener.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.widget.multiwaveview;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map.Entry;
-
-import android.animation.Animator.AnimatorListener;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.util.Log;
-
-class Tweener {
- private static final String TAG = "Tweener";
- private static final boolean DEBUG = false;
-
- ObjectAnimator animator;
- private static HashMap<Object, Tweener> sTweens = new HashMap<Object, Tweener>();
-
- public Tweener(ObjectAnimator anim) {
- animator = anim;
- }
-
- private static void remove(Animator animator) {
- Iterator<Entry<Object, Tweener>> iter = sTweens.entrySet().iterator();
- while (iter.hasNext()) {
- Entry<Object, Tweener> entry = iter.next();
- if (entry.getValue().animator == animator) {
- if (DEBUG) Log.v(TAG, "Removing tweener " + sTweens.get(entry.getKey())
- + " sTweens.size() = " + sTweens.size());
- iter.remove();
- break; // an animator can only be attached to one object
- }
- }
- }
-
- public static Tweener to(Object object, long duration, Object... vars) {
- long delay = 0;
- AnimatorUpdateListener updateListener = null;
- AnimatorListener listener = null;
- TimeInterpolator interpolator = null;
-
- // Iterate through arguments and discover properties to animate
- ArrayList<PropertyValuesHolder> props = new ArrayList<PropertyValuesHolder>(vars.length/2);
- for (int i = 0; i < vars.length; i+=2) {
- if (!(vars[i] instanceof String)) {
- throw new IllegalArgumentException("Key must be a string: " + vars[i]);
- }
- String key = (String) vars[i];
- Object value = vars[i+1];
- if ("simultaneousTween".equals(key)) {
- // TODO
- } else if ("ease".equals(key)) {
- interpolator = (TimeInterpolator) value; // TODO: multiple interpolators?
- } else if ("onUpdate".equals(key) || "onUpdateListener".equals(key)) {
- updateListener = (AnimatorUpdateListener) value;
- } else if ("onComplete".equals(key) || "onCompleteListener".equals(key)) {
- listener = (AnimatorListener) value;
- } else if ("delay".equals(key)) {
- delay = ((Number) value).longValue();
- } else if ("syncWith".equals(key)) {
- // TODO
- } else if (value instanceof float[]) {
- props.add(PropertyValuesHolder.ofFloat(key,
- ((float[])value)[0], ((float[])value)[1]));
- } else if (value instanceof int[]) {
- props.add(PropertyValuesHolder.ofInt(key,
- ((int[])value)[0], ((int[])value)[1]));
- } else if (value instanceof Number) {
- float floatValue = ((Number)value).floatValue();
- props.add(PropertyValuesHolder.ofFloat(key, floatValue));
- } else {
- throw new IllegalArgumentException(
- "Bad argument for key \"" + key + "\" with value " + value.getClass());
- }
- }
-
- // Re-use existing tween, if present
- Tweener tween = sTweens.get(object);
- ObjectAnimator anim = null;
- if (tween == null) {
- anim = ObjectAnimator.ofPropertyValuesHolder(object,
- props.toArray(new PropertyValuesHolder[props.size()]));
- tween = new Tweener(anim);
- sTweens.put(object, tween);
- if (DEBUG) Log.v(TAG, "Added new Tweener " + tween);
- } else {
- anim = sTweens.get(object).animator;
- replace(props, object); // Cancel all animators for given object
- }
-
- if (interpolator != null) {
- anim.setInterpolator(interpolator);
- }
-
- // Update animation with properties discovered in loop above
- anim.setStartDelay(delay);
- anim.setDuration(duration);
- if (updateListener != null) {
- anim.removeAllUpdateListeners(); // There should be only one
- anim.addUpdateListener(updateListener);
- }
- if (listener != null) {
- anim.removeAllListeners(); // There should be only one.
- anim.addListener(listener);
- }
- anim.addListener(mCleanupListener);
-
- return tween;
- }
-
- Tweener from(Object object, long duration, Object... vars) {
- // TODO: for v of vars
- // toVars[v] = object[v]
- // object[v] = vars[v]
- return Tweener.to(object, duration, vars);
- }
-
- // Listener to watch for completed animations and remove them.
- private static AnimatorListener mCleanupListener = new AnimatorListenerAdapter() {
-
- @Override
- public void onAnimationEnd(Animator animation) {
- remove(animation);
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- remove(animation);
- }
- };
-
- public static void reset() {
- if (DEBUG) {
- Log.v(TAG, "Reset()");
- if (sTweens.size() > 0) {
- Log.v(TAG, "Cleaning up " + sTweens.size() + " animations");
- }
- }
- sTweens.clear();
- }
-
- private static void replace(ArrayList<PropertyValuesHolder> props, Object... args) {
- for (final Object killobject : args) {
- Tweener tween = sTweens.get(killobject);
- if (tween != null) {
- tween.animator.cancel();
- if (props != null) {
- tween.animator.setValues(
- props.toArray(new PropertyValuesHolder[props.size()]));
- } else {
- sTweens.remove(tween);
- }
- }
- }
- }
-}
diff --git a/core/java/com/android/server/backup/AccountSyncSettingsBackupHelper.java b/core/java/com/android/server/backup/AccountSyncSettingsBackupHelper.java
new file mode 100644
index 0000000..3f4b980
--- /dev/null
+++ b/core/java/com/android/server/backup/AccountSyncSettingsBackupHelper.java
@@ -0,0 +1,380 @@
+/*
+ * 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 com.android.server.backup;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.app.backup.BackupDataInputStream;
+import android.app.backup.BackupDataOutput;
+import android.app.backup.BackupHelper;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.SyncAdapterType;
+import android.content.SyncStatusObserver;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.BufferedOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.EOFException;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Helper for backing up account sync settings (whether or not a service should be synced). The
+ * sync settings are backed up as a JSON object containing all the necessary information for
+ * restoring the sync settings later.
+ */
+public class AccountSyncSettingsBackupHelper implements BackupHelper {
+
+ private static final String TAG = "AccountSyncSettingsBackupHelper";
+ private static final boolean DEBUG = false;
+
+ private static final int STATE_VERSION = 1;
+ private static final int MD5_BYTE_SIZE = 16;
+ private static final int SYNC_REQUEST_LATCH_TIMEOUT_SECONDS = 1;
+
+ private static final String JSON_FORMAT_HEADER_KEY = "account_data";
+ private static final String JSON_FORMAT_ENCODING = "UTF-8";
+ private static final int JSON_FORMAT_VERSION = 1;
+
+ private static final String KEY_VERSION = "version";
+ private static final String KEY_MASTER_SYNC_ENABLED = "masterSyncEnabled";
+ private static final String KEY_ACCOUNTS = "accounts";
+ private static final String KEY_ACCOUNT_NAME = "name";
+ private static final String KEY_ACCOUNT_TYPE = "type";
+ private static final String KEY_ACCOUNT_AUTHORITIES = "authorities";
+ private static final String KEY_AUTHORITY_NAME = "name";
+ private static final String KEY_AUTHORITY_SYNC_STATE = "syncState";
+ private static final String KEY_AUTHORITY_SYNC_ENABLED = "syncEnabled";
+
+ private Context mContext;
+ private AccountManager mAccountManager;
+
+ public AccountSyncSettingsBackupHelper(Context context) {
+ mContext = context;
+ mAccountManager = AccountManager.get(mContext);
+ }
+
+ /**
+ * Take a snapshot of the current account sync settings and write them to the given output.
+ */
+ @Override
+ public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput output,
+ ParcelFileDescriptor newState) {
+ try {
+ JSONObject dataJSON = serializeAccountSyncSettingsToJSON();
+
+ if (DEBUG) {
+ Log.d(TAG, "Account sync settings JSON: " + dataJSON);
+ }
+
+ // Encode JSON data to bytes.
+ byte[] dataBytes = dataJSON.toString().getBytes(JSON_FORMAT_ENCODING);
+ byte[] oldMd5Checksum = readOldMd5Checksum(oldState);
+ byte[] newMd5Checksum = generateMd5Checksum(dataBytes);
+ if (!Arrays.equals(oldMd5Checksum, newMd5Checksum)) {
+ int dataSize = dataBytes.length;
+ output.writeEntityHeader(JSON_FORMAT_HEADER_KEY, dataSize);
+ output.writeEntityData(dataBytes, dataSize);
+
+ Log.i(TAG, "Backup successful.");
+ } else {
+ Log.i(TAG, "Old and new MD5 checksums match. Skipping backup.");
+ }
+
+ writeNewMd5Checksum(newState, newMd5Checksum);
+ } catch (JSONException | IOException | NoSuchAlgorithmException e) {
+ Log.e(TAG, "Couldn't backup account sync settings\n" + e);
+ }
+ }
+
+ /**
+ * Fetch and serialize Account and authority information as a JSON Array.
+ */
+ private JSONObject serializeAccountSyncSettingsToJSON() throws JSONException {
+ Account[] accounts = mAccountManager.getAccounts();
+ SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypesAsUser(
+ mContext.getUserId());
+
+ // Create a map of Account types to authorities. Later this will make it easier for us to
+ // generate our JSON.
+ HashMap<String, List<String>> accountTypeToAuthorities = new HashMap<String,
+ List<String>>();
+ for (SyncAdapterType syncAdapter : syncAdapters) {
+ // Skip adapters that aren’t visible to the user.
+ if (!syncAdapter.isUserVisible()) {
+ continue;
+ }
+ if (!accountTypeToAuthorities.containsKey(syncAdapter.accountType)) {
+ accountTypeToAuthorities.put(syncAdapter.accountType, new ArrayList<String>());
+ }
+ accountTypeToAuthorities.get(syncAdapter.accountType).add(syncAdapter.authority);
+ }
+
+ // Generate JSON.
+ JSONObject backupJSON = new JSONObject();
+ backupJSON.put(KEY_VERSION, JSON_FORMAT_VERSION);
+ backupJSON.put(KEY_MASTER_SYNC_ENABLED, ContentResolver.getMasterSyncAutomatically());
+
+ JSONArray accountJSONArray = new JSONArray();
+ for (Account account : accounts) {
+ List<String> authorities = accountTypeToAuthorities.get(account.type);
+
+ // We ignore Accounts that don't have any authorities because there would be no sync
+ // settings for us to restore.
+ if (authorities == null || authorities.isEmpty()) {
+ continue;
+ }
+
+ JSONObject accountJSON = new JSONObject();
+ accountJSON.put(KEY_ACCOUNT_NAME, account.name);
+ accountJSON.put(KEY_ACCOUNT_TYPE, account.type);
+
+ // Add authorities for this Account type and check whether or not sync is enabled.
+ JSONArray authoritiesJSONArray = new JSONArray();
+ for (String authority : authorities) {
+ int syncState = ContentResolver.getIsSyncable(account, authority);
+ boolean syncEnabled = ContentResolver.getSyncAutomatically(account, authority);
+
+ JSONObject authorityJSON = new JSONObject();
+ authorityJSON.put(KEY_AUTHORITY_NAME, authority);
+ authorityJSON.put(KEY_AUTHORITY_SYNC_STATE, syncState);
+ authorityJSON.put(KEY_AUTHORITY_SYNC_ENABLED, syncEnabled);
+ authoritiesJSONArray.put(authorityJSON);
+ }
+ accountJSON.put(KEY_ACCOUNT_AUTHORITIES, authoritiesJSONArray);
+
+ accountJSONArray.put(accountJSON);
+ }
+ backupJSON.put(KEY_ACCOUNTS, accountJSONArray);
+
+ return backupJSON;
+ }
+
+ /**
+ * Read the MD5 checksum from the old state.
+ *
+ * @return the old MD5 checksum
+ */
+ private byte[] readOldMd5Checksum(ParcelFileDescriptor oldState) throws IOException {
+ DataInputStream dataInput = new DataInputStream(
+ new FileInputStream(oldState.getFileDescriptor()));
+
+ byte[] oldMd5Checksum = new byte[MD5_BYTE_SIZE];
+ try {
+ int stateVersion = dataInput.readInt();
+ if (stateVersion <= STATE_VERSION) {
+ // If the state version is a version we can understand then read the MD5 sum,
+ // otherwise we return an empty byte array for the MD5 sum which will force a
+ // backup.
+ for (int i = 0; i < MD5_BYTE_SIZE; i++) {
+ oldMd5Checksum[i] = dataInput.readByte();
+ }
+ } else {
+ Log.i(TAG, "Backup state version is: " + stateVersion
+ + " (support only up to version " + STATE_VERSION + ")");
+ }
+ } catch (EOFException eof) {
+ // Initial state may be empty.
+ } finally {
+ dataInput.close();
+ }
+ return oldMd5Checksum;
+ }
+
+ /**
+ * Write the given checksum to the file descriptor.
+ */
+ private void writeNewMd5Checksum(ParcelFileDescriptor newState, byte[] md5Checksum)
+ throws IOException {
+ DataOutputStream dataOutput = new DataOutputStream(
+ new BufferedOutputStream(new FileOutputStream(newState.getFileDescriptor())));
+
+ dataOutput.writeInt(STATE_VERSION);
+ dataOutput.write(md5Checksum);
+ dataOutput.close();
+ }
+
+ private byte[] generateMd5Checksum(byte[] data) throws NoSuchAlgorithmException {
+ if (data == null) {
+ return null;
+ }
+
+ MessageDigest md5 = MessageDigest.getInstance("MD5");
+ return md5.digest(data);
+ }
+
+ /**
+ * Restore account sync settings from the given data input stream.
+ */
+ @Override
+ public void restoreEntity(BackupDataInputStream data) {
+ byte[] dataBytes = new byte[data.size()];
+ try {
+ // Read the data and convert it to a String.
+ data.read(dataBytes);
+ String dataString = new String(dataBytes, JSON_FORMAT_ENCODING);
+
+ // Convert data to a JSON object.
+ JSONObject dataJSON = new JSONObject(dataString);
+ boolean masterSyncEnabled = dataJSON.getBoolean(KEY_MASTER_SYNC_ENABLED);
+ JSONArray accountJSONArray = dataJSON.getJSONArray(KEY_ACCOUNTS);
+
+ boolean currentMasterSyncEnabled = ContentResolver.getMasterSyncAutomatically();
+ if (currentMasterSyncEnabled) {
+ // Disable master sync to prevent any syncs from running.
+ ContentResolver.setMasterSyncAutomatically(false);
+ }
+
+ try {
+ HashSet<Account> currentAccounts = getAccountsHashSet();
+ for (int i = 0; i < accountJSONArray.length(); i++) {
+ JSONObject accountJSON = (JSONObject) accountJSONArray.get(i);
+ String accountName = accountJSON.getString(KEY_ACCOUNT_NAME);
+ String accountType = accountJSON.getString(KEY_ACCOUNT_TYPE);
+
+ Account account = new Account(accountName, accountType);
+
+ // Check if the account already exists. Accounts that don't exist on the device
+ // yet won't be restored.
+ if (currentAccounts.contains(account)) {
+ restoreExistingAccountSyncSettingsFromJSON(accountJSON);
+ }
+ }
+ } finally {
+ // Set the master sync preference to the value from the backup set.
+ ContentResolver.setMasterSyncAutomatically(masterSyncEnabled);
+ }
+
+ Log.i(TAG, "Restore successful.");
+ } catch (IOException | JSONException e) {
+ Log.e(TAG, "Couldn't restore account sync settings\n" + e);
+ }
+ }
+
+ /**
+ * Helper method - fetch accounts and return them as a HashSet.
+ *
+ * @return Accounts in a HashSet.
+ */
+ private HashSet<Account> getAccountsHashSet() {
+ Account[] accounts = mAccountManager.getAccounts();
+ HashSet<Account> accountHashSet = new HashSet<Account>();
+ for (Account account : accounts) {
+ accountHashSet.add(account);
+ }
+ return accountHashSet;
+ }
+
+ /**
+ * Restore account sync settings using the given JSON. This function won't work if the account
+ * doesn't exist yet.
+ */
+ private void restoreExistingAccountSyncSettingsFromJSON(JSONObject accountJSON)
+ throws JSONException {
+ // Restore authorities.
+ JSONArray authorities = accountJSON.getJSONArray(KEY_ACCOUNT_AUTHORITIES);
+ String accountName = accountJSON.getString(KEY_ACCOUNT_NAME);
+ String accountType = accountJSON.getString(KEY_ACCOUNT_TYPE);
+ final Account account = new Account(accountName, accountType);
+ for (int i = 0; i < authorities.length(); i++) {
+ JSONObject authority = (JSONObject) authorities.get(i);
+ final String authorityName = authority.getString(KEY_AUTHORITY_NAME);
+ boolean syncEnabled = authority.getBoolean(KEY_AUTHORITY_SYNC_ENABLED);
+
+ // Cancel any active syncs.
+ if (ContentResolver.isSyncActive(account, authorityName)) {
+ ContentResolver.cancelSync(account, authorityName);
+ }
+
+ boolean overwriteSync = true;
+ Bundle initializationExtras = createSyncInitializationBundle();
+ int currentSyncState = ContentResolver.getIsSyncable(account, authorityName);
+ if (currentSyncState < 0) {
+ // Requesting a sync is an asynchronous operation, so we setup a countdown latch to
+ // wait for it to finish. Initialization syncs are generally very brief and
+ // shouldn't take too much time to finish.
+ final CountDownLatch latch = new CountDownLatch(1);
+ Object syncStatusObserverHandle = ContentResolver.addStatusChangeListener(
+ ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE, new SyncStatusObserver() {
+ @Override
+ public void onStatusChanged(int which) {
+ if (!ContentResolver.isSyncActive(account, authorityName)) {
+ latch.countDown();
+ }
+ }
+ });
+
+ // If we set sync settings for a sync that hasn't been initialized yet, we run the
+ // risk of having our changes overwritten later on when the sync gets initialized.
+ // To prevent this from happening we will manually initiate the sync adapter. We
+ // also explicitly pass in a Bundle with SYNC_EXTRAS_INITIALIZE to prevent a data
+ // sync from running after the initialization sync. Two syncs will be scheduled, but
+ // the second one (data sync) will override the first one (initialization sync) and
+ // still behave as an initialization sync because of the Bundle.
+ ContentResolver.requestSync(account, authorityName, initializationExtras);
+
+ boolean done = false;
+ try {
+ done = latch.await(SYNC_REQUEST_LATCH_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ Log.e(TAG, "CountDownLatch interrupted\n" + e);
+ done = false;
+ }
+ if (!done) {
+ overwriteSync = false;
+ Log.i(TAG, "CountDownLatch timed out, skipping '" + authorityName
+ + "' authority.");
+ }
+ ContentResolver.removeStatusChangeListener(syncStatusObserverHandle);
+ }
+
+ if (overwriteSync) {
+ ContentResolver.setSyncAutomatically(account, authorityName, syncEnabled);
+ Log.i(TAG, "Set sync automatically for '" + authorityName + "': " + syncEnabled);
+ }
+ }
+ }
+
+ private Bundle createSyncInitializationBundle() {
+ Bundle extras = new Bundle();
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
+ return extras;
+ }
+
+ @Override
+ public void writeNewStateDescription(ParcelFileDescriptor newState) {
+
+ }
+}
diff --git a/core/java/com/android/server/backup/SystemBackupAgent.java b/core/java/com/android/server/backup/SystemBackupAgent.java
index 35a1a5a..b5f2f37 100644
--- a/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -86,6 +86,8 @@ public class SystemBackupAgent extends BackupAgentHelper {
}
addHelper("wallpaper", new WallpaperBackupHelper(SystemBackupAgent.this, files, keys));
addHelper("recents", new RecentsBackupHelper(SystemBackupAgent.this));
+ addHelper("account_sync_settings",
+ new AccountSyncSettingsBackupHelper(SystemBackupAgent.this));
super.onBackup(oldState, data, newState);
}
@@ -118,6 +120,8 @@ public class SystemBackupAgent extends BackupAgentHelper {
new String[] { WALLPAPER_IMAGE },
new String[] { WALLPAPER_IMAGE_KEY} ));
addHelper("recents", new RecentsBackupHelper(SystemBackupAgent.this));
+ addHelper("account_sync_settings",
+ new AccountSyncSettingsBackupHelper(SystemBackupAgent.this));
try {
super.onRestore(data, appVersionCode, newState);