Merge "Implement time markers for TTS."
diff --git a/api/current.txt b/api/current.txt
index bab282d..2fe3f44 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1817,6 +1817,7 @@
field public static final int tabs = 16908307; // 0x1020013
field public static final int text1 = 16908308; // 0x1020014
field public static final int text2 = 16908309; // 0x1020015
+ field public static final int textAssist = 16908353; // 0x1020041
field public static final int title = 16908310; // 0x1020016
field public static final int toggle = 16908311; // 0x1020017
field public static final int undo = 16908338; // 0x1020032
@@ -3536,7 +3537,6 @@
method public int getRequestedOrientation();
method public final android.view.SearchEvent getSearchEvent();
method public int getTaskId();
- method public android.text.TextAssistant getTextAssistant();
method public final java.lang.CharSequence getTitle();
method public final int getTitleColor();
method public android.app.VoiceInteractor getVoiceInteractor();
@@ -3686,7 +3686,6 @@
method public final void setResult(int, android.content.Intent);
method public final deprecated void setSecondaryProgress(int);
method public void setTaskDescription(android.app.ActivityManager.TaskDescription);
- method public void setTextAssistant(android.text.TextAssistant);
method public void setTitle(java.lang.CharSequence);
method public void setTitle(int);
method public deprecated void setTitleColor(int);
@@ -9760,6 +9759,7 @@
field public boolean enabled;
field public boolean exported;
field public java.lang.String processName;
+ field public java.lang.String splitName;
}
public class ConfigurationInfo implements android.os.Parcelable {
@@ -35788,6 +35788,7 @@
public static class NotificationListenerService.Ranking {
ctor public NotificationListenerService.Ranking();
+ method public boolean canShowBadge();
method public java.util.List<java.lang.String> getAdditionalPeople();
method public android.app.NotificationChannel getChannel();
method public int getImportance();
@@ -35829,7 +35830,6 @@
method public int getId();
method public java.lang.String getKey();
method public android.app.Notification getNotification();
- method public android.app.NotificationChannel getNotificationChannel();
method public java.lang.String getOverrideGroupKey();
method public java.lang.String getPackageName();
method public long getPostTime();
@@ -38490,6 +38490,7 @@
method public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(java.lang.String);
method public java.lang.String iccTransmitApduBasicChannel(int, int, int, int, int, java.lang.String);
method public java.lang.String iccTransmitApduLogicalChannel(int, int, int, int, int, int, java.lang.String);
+ method public boolean isConcurrentVoiceAndDataAllowed();
method public boolean isHearingAidCompatibilitySupported();
method public boolean isNetworkRoaming();
method public boolean isSmsCapable();
@@ -39927,22 +39928,6 @@
method public android.text.StaticLayout.Builder setTextDirection(android.text.TextDirectionHeuristic);
}
- public abstract interface TextAssistant {
- method public abstract void addLinks(android.text.Spannable, int);
- method public abstract android.text.TextSelection suggestSelection(java.lang.CharSequence, int, int);
- }
-
- public class TextClassification {
- ctor public TextClassification();
- method public java.util.Map<java.lang.String, java.lang.Float> getTypeConfidence();
- }
-
- public final class TextClassificationManager implements android.text.TextAssistant {
- method public void addLinks(android.text.Spannable, int);
- method public java.util.List<android.text.TextLanguage> detectLanguages(java.lang.CharSequence);
- method public android.text.TextSelection suggestSelection(java.lang.CharSequence, int, int);
- }
-
public abstract interface TextDirectionHeuristic {
method public abstract boolean isRtl(char[], int, int);
method public abstract boolean isRtl(java.lang.CharSequence, int, int);
@@ -39958,13 +39943,6 @@
field public static final android.text.TextDirectionHeuristic RTL;
}
- public final class TextLanguage {
- ctor public TextLanguage(int, int, java.util.Map<java.lang.String, java.lang.Float>);
- method public int getEndIndex();
- method public java.util.Map<java.lang.String, java.lang.Float> getLanguageConfidence();
- method public int getStartIndex();
- }
-
public class TextPaint extends android.graphics.Paint {
ctor public TextPaint();
ctor public TextPaint(int);
@@ -39977,13 +39955,6 @@
field public int linkColor;
}
- public class TextSelection {
- ctor public TextSelection();
- method public int getSelectionEndIndex();
- method public int getSelectionStartIndex();
- method public android.text.TextClassification getTextClassification();
- }
-
public class TextUtils {
method public static deprecated java.lang.CharSequence commaEllipsize(java.lang.CharSequence, android.text.TextPaint, float, java.lang.String, java.lang.String);
method public static java.lang.CharSequence concat(java.lang.CharSequence...);
@@ -46495,6 +46466,83 @@
}
+package android.view.textclassifier {
+
+ public abstract interface LinksInfo {
+ method public abstract boolean apply(java.lang.CharSequence);
+ }
+
+ public final class TextClassificationManager {
+ method public java.util.List<android.view.textclassifier.TextLanguage> detectLanguages(java.lang.CharSequence);
+ method public android.view.textclassifier.TextClassifier getDefaultTextClassifier();
+ }
+
+ public final class TextClassificationResult {
+ method public float getConfidenceScore(java.lang.String);
+ method public java.lang.String getEntity(int);
+ method public int getEntityCount();
+ method public android.graphics.drawable.Drawable getIcon();
+ method public android.content.Intent getIntent();
+ method public java.lang.CharSequence getLabel();
+ method public android.view.View.OnClickListener getOnClickListener();
+ method public java.lang.String getText();
+ }
+
+ public static final class TextClassificationResult.Builder {
+ ctor public TextClassificationResult.Builder();
+ method public android.view.textclassifier.TextClassificationResult build();
+ method public android.view.textclassifier.TextClassificationResult.Builder setEntityType(java.lang.String, float);
+ method public android.view.textclassifier.TextClassificationResult.Builder setIcon(android.graphics.drawable.Drawable);
+ method public android.view.textclassifier.TextClassificationResult.Builder setIntent(android.content.Intent);
+ method public android.view.textclassifier.TextClassificationResult.Builder setLabel(java.lang.String);
+ method public android.view.textclassifier.TextClassificationResult.Builder setOnClickListener(android.view.View.OnClickListener);
+ method public android.view.textclassifier.TextClassificationResult.Builder setText(java.lang.String);
+ }
+
+ public abstract interface TextClassifier {
+ method public abstract android.view.textclassifier.LinksInfo getLinks(java.lang.CharSequence, int);
+ method public abstract android.view.textclassifier.TextClassificationResult getTextClassificationResult(java.lang.CharSequence, int, int);
+ method public abstract android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int);
+ field public static final android.view.textclassifier.TextClassifier NO_OP;
+ field public static final java.lang.String TYPE_ADDRESS = "address";
+ field public static final java.lang.String TYPE_EMAIL = "email";
+ field public static final java.lang.String TYPE_OTHER = "other";
+ field public static final java.lang.String TYPE_PHONE = "phone";
+ }
+
+ public static abstract class TextClassifier.EntityType implements java.lang.annotation.Annotation {
+ }
+
+ public final class TextLanguage {
+ method public float getConfidenceScore(java.util.Locale);
+ method public int getEndIndex();
+ method public java.util.Locale getLanguage(int);
+ method public int getLanguageCount();
+ method public int getStartIndex();
+ }
+
+ public static final class TextLanguage.Builder {
+ ctor public TextLanguage.Builder(int, int);
+ method public android.view.textclassifier.TextLanguage build();
+ method public android.view.textclassifier.TextLanguage.Builder setLanguage(java.util.Locale, float);
+ }
+
+ public final class TextSelection {
+ method public float getConfidenceScore(java.lang.String);
+ method public java.lang.String getEntity(int);
+ method public int getEntityCount();
+ method public int getSelectionEndIndex();
+ method public int getSelectionStartIndex();
+ }
+
+ public static final class TextSelection.Builder {
+ ctor public TextSelection.Builder(int, int);
+ method public android.view.textclassifier.TextSelection build();
+ method public android.view.textclassifier.TextSelection.Builder setEntityType(java.lang.String, float);
+ }
+
+}
+
package android.view.textservice {
public final class SentenceSuggestionsInfo implements android.os.Parcelable {
@@ -49584,7 +49632,7 @@
method public float getShadowRadius();
method public final boolean getShowSoftInputOnFocus();
method public java.lang.CharSequence getText();
- method public android.text.TextAssistant getTextAssistant();
+ method public android.view.textclassifier.TextClassifier getTextClassifier();
method public final android.content.res.ColorStateList getTextColors();
method public java.util.Locale getTextLocale();
method public android.os.LocaleList getTextLocales();
@@ -49700,7 +49748,7 @@
method public final void setText(int, android.widget.TextView.BufferType);
method public void setTextAppearance(int);
method public deprecated void setTextAppearance(android.content.Context, int);
- method public void setTextAssistant(android.text.TextAssistant);
+ method public void setTextClassifier(android.view.textclassifier.TextClassifier);
method public void setTextColor(int);
method public void setTextColor(android.content.res.ColorStateList);
method public void setTextIsSelectable(boolean);
diff --git a/api/system-current.txt b/api/system-current.txt
index c539355d..7a6d87a 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1930,6 +1930,7 @@
field public static final int tabs = 16908307; // 0x1020013
field public static final int text1 = 16908308; // 0x1020014
field public static final int text2 = 16908309; // 0x1020015
+ field public static final int textAssist = 16908353; // 0x1020041
field public static final int title = 16908310; // 0x1020016
field public static final int toggle = 16908311; // 0x1020017
field public static final int undo = 16908338; // 0x1020032
@@ -3655,7 +3656,6 @@
method public int getRequestedOrientation();
method public final android.view.SearchEvent getSearchEvent();
method public int getTaskId();
- method public android.text.TextAssistant getTextAssistant();
method public final java.lang.CharSequence getTitle();
method public final int getTitleColor();
method public android.app.VoiceInteractor getVoiceInteractor();
@@ -3807,7 +3807,6 @@
method public final void setResult(int, android.content.Intent);
method public final deprecated void setSecondaryProgress(int);
method public void setTaskDescription(android.app.ActivityManager.TaskDescription);
- method public void setTextAssistant(android.text.TextAssistant);
method public void setTitle(java.lang.CharSequence);
method public void setTitle(int);
method public deprecated void setTitleColor(int);
@@ -10164,6 +10163,7 @@
field public boolean enabled;
field public boolean exported;
field public java.lang.String processName;
+ field public java.lang.String splitName;
}
public class ConfigurationInfo implements android.os.Parcelable {
@@ -38733,6 +38733,7 @@
public static class NotificationListenerService.Ranking {
ctor public NotificationListenerService.Ranking();
+ method public boolean canShowBadge();
method public java.util.List<java.lang.String> getAdditionalPeople();
method public android.app.NotificationChannel getChannel();
method public int getImportance();
@@ -38774,7 +38775,6 @@
method public int getId();
method public java.lang.String getKey();
method public android.app.Notification getNotification();
- method public android.app.NotificationChannel getNotificationChannel();
method public java.lang.String getOverrideGroupKey();
method public java.lang.String getPackageName();
method public long getPostTime();
@@ -41746,6 +41746,7 @@
method public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(java.lang.String);
method public java.lang.String iccTransmitApduBasicChannel(int, int, int, int, int, java.lang.String);
method public java.lang.String iccTransmitApduLogicalChannel(int, int, int, int, int, int, java.lang.String);
+ method public boolean isConcurrentVoiceAndDataAllowed();
method public boolean isDataConnectivityPossible();
method public boolean isHearingAidCompatibilitySupported();
method public boolean isIdle();
@@ -43230,22 +43231,6 @@
method public android.text.StaticLayout.Builder setTextDirection(android.text.TextDirectionHeuristic);
}
- public abstract interface TextAssistant {
- method public abstract void addLinks(android.text.Spannable, int);
- method public abstract android.text.TextSelection suggestSelection(java.lang.CharSequence, int, int);
- }
-
- public class TextClassification {
- ctor public TextClassification();
- method public java.util.Map<java.lang.String, java.lang.Float> getTypeConfidence();
- }
-
- public final class TextClassificationManager implements android.text.TextAssistant {
- method public void addLinks(android.text.Spannable, int);
- method public java.util.List<android.text.TextLanguage> detectLanguages(java.lang.CharSequence);
- method public android.text.TextSelection suggestSelection(java.lang.CharSequence, int, int);
- }
-
public abstract interface TextDirectionHeuristic {
method public abstract boolean isRtl(char[], int, int);
method public abstract boolean isRtl(java.lang.CharSequence, int, int);
@@ -43261,13 +43246,6 @@
field public static final android.text.TextDirectionHeuristic RTL;
}
- public final class TextLanguage {
- ctor public TextLanguage(int, int, java.util.Map<java.lang.String, java.lang.Float>);
- method public int getEndIndex();
- method public java.util.Map<java.lang.String, java.lang.Float> getLanguageConfidence();
- method public int getStartIndex();
- }
-
public class TextPaint extends android.graphics.Paint {
ctor public TextPaint();
ctor public TextPaint(int);
@@ -43280,13 +43258,6 @@
field public int linkColor;
}
- public class TextSelection {
- ctor public TextSelection();
- method public int getSelectionEndIndex();
- method public int getSelectionStartIndex();
- method public android.text.TextClassification getTextClassification();
- }
-
public class TextUtils {
method public static deprecated java.lang.CharSequence commaEllipsize(java.lang.CharSequence, android.text.TextPaint, float, java.lang.String, java.lang.String);
method public static java.lang.CharSequence concat(java.lang.CharSequence...);
@@ -49802,6 +49773,83 @@
}
+package android.view.textclassifier {
+
+ public abstract interface LinksInfo {
+ method public abstract boolean apply(java.lang.CharSequence);
+ }
+
+ public final class TextClassificationManager {
+ method public java.util.List<android.view.textclassifier.TextLanguage> detectLanguages(java.lang.CharSequence);
+ method public android.view.textclassifier.TextClassifier getDefaultTextClassifier();
+ }
+
+ public final class TextClassificationResult {
+ method public float getConfidenceScore(java.lang.String);
+ method public java.lang.String getEntity(int);
+ method public int getEntityCount();
+ method public android.graphics.drawable.Drawable getIcon();
+ method public android.content.Intent getIntent();
+ method public java.lang.CharSequence getLabel();
+ method public android.view.View.OnClickListener getOnClickListener();
+ method public java.lang.String getText();
+ }
+
+ public static final class TextClassificationResult.Builder {
+ ctor public TextClassificationResult.Builder();
+ method public android.view.textclassifier.TextClassificationResult build();
+ method public android.view.textclassifier.TextClassificationResult.Builder setEntityType(java.lang.String, float);
+ method public android.view.textclassifier.TextClassificationResult.Builder setIcon(android.graphics.drawable.Drawable);
+ method public android.view.textclassifier.TextClassificationResult.Builder setIntent(android.content.Intent);
+ method public android.view.textclassifier.TextClassificationResult.Builder setLabel(java.lang.String);
+ method public android.view.textclassifier.TextClassificationResult.Builder setOnClickListener(android.view.View.OnClickListener);
+ method public android.view.textclassifier.TextClassificationResult.Builder setText(java.lang.String);
+ }
+
+ public abstract interface TextClassifier {
+ method public abstract android.view.textclassifier.LinksInfo getLinks(java.lang.CharSequence, int);
+ method public abstract android.view.textclassifier.TextClassificationResult getTextClassificationResult(java.lang.CharSequence, int, int);
+ method public abstract android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int);
+ field public static final android.view.textclassifier.TextClassifier NO_OP;
+ field public static final java.lang.String TYPE_ADDRESS = "address";
+ field public static final java.lang.String TYPE_EMAIL = "email";
+ field public static final java.lang.String TYPE_OTHER = "other";
+ field public static final java.lang.String TYPE_PHONE = "phone";
+ }
+
+ public static abstract class TextClassifier.EntityType implements java.lang.annotation.Annotation {
+ }
+
+ public final class TextLanguage {
+ method public float getConfidenceScore(java.util.Locale);
+ method public int getEndIndex();
+ method public java.util.Locale getLanguage(int);
+ method public int getLanguageCount();
+ method public int getStartIndex();
+ }
+
+ public static final class TextLanguage.Builder {
+ ctor public TextLanguage.Builder(int, int);
+ method public android.view.textclassifier.TextLanguage build();
+ method public android.view.textclassifier.TextLanguage.Builder setLanguage(java.util.Locale, float);
+ }
+
+ public final class TextSelection {
+ method public float getConfidenceScore(java.lang.String);
+ method public java.lang.String getEntity(int);
+ method public int getEntityCount();
+ method public int getSelectionEndIndex();
+ method public int getSelectionStartIndex();
+ }
+
+ public static final class TextSelection.Builder {
+ ctor public TextSelection.Builder(int, int);
+ method public android.view.textclassifier.TextSelection build();
+ method public android.view.textclassifier.TextSelection.Builder setEntityType(java.lang.String, float);
+ }
+
+}
+
package android.view.textservice {
public final class SentenceSuggestionsInfo implements android.os.Parcelable {
@@ -53252,7 +53300,7 @@
method public float getShadowRadius();
method public final boolean getShowSoftInputOnFocus();
method public java.lang.CharSequence getText();
- method public android.text.TextAssistant getTextAssistant();
+ method public android.view.textclassifier.TextClassifier getTextClassifier();
method public final android.content.res.ColorStateList getTextColors();
method public java.util.Locale getTextLocale();
method public android.os.LocaleList getTextLocales();
@@ -53368,7 +53416,7 @@
method public final void setText(int, android.widget.TextView.BufferType);
method public void setTextAppearance(int);
method public deprecated void setTextAppearance(android.content.Context, int);
- method public void setTextAssistant(android.text.TextAssistant);
+ method public void setTextClassifier(android.view.textclassifier.TextClassifier);
method public void setTextColor(int);
method public void setTextColor(android.content.res.ColorStateList);
method public void setTextIsSelectable(boolean);
diff --git a/api/test-current.txt b/api/test-current.txt
index 2c2f143e..cd71166 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1817,6 +1817,7 @@
field public static final int tabs = 16908307; // 0x1020013
field public static final int text1 = 16908308; // 0x1020014
field public static final int text2 = 16908309; // 0x1020015
+ field public static final int textAssist = 16908353; // 0x1020041
field public static final int title = 16908310; // 0x1020016
field public static final int toggle = 16908311; // 0x1020017
field public static final int undo = 16908338; // 0x1020032
@@ -3538,7 +3539,6 @@
method public int getRequestedOrientation();
method public final android.view.SearchEvent getSearchEvent();
method public int getTaskId();
- method public android.text.TextAssistant getTextAssistant();
method public final java.lang.CharSequence getTitle();
method public final int getTitleColor();
method public android.app.VoiceInteractor getVoiceInteractor();
@@ -3688,7 +3688,6 @@
method public final void setResult(int, android.content.Intent);
method public final deprecated void setSecondaryProgress(int);
method public void setTaskDescription(android.app.ActivityManager.TaskDescription);
- method public void setTextAssistant(android.text.TextAssistant);
method public void setTitle(java.lang.CharSequence);
method public void setTitle(int);
method public deprecated void setTitleColor(int);
@@ -9787,6 +9786,7 @@
field public boolean enabled;
field public boolean exported;
field public java.lang.String processName;
+ field public java.lang.String splitName;
}
public class ConfigurationInfo implements android.os.Parcelable {
@@ -35909,6 +35909,7 @@
public static class NotificationListenerService.Ranking {
ctor public NotificationListenerService.Ranking();
+ method public boolean canShowBadge();
method public java.util.List<java.lang.String> getAdditionalPeople();
method public android.app.NotificationChannel getChannel();
method public int getImportance();
@@ -35950,7 +35951,6 @@
method public int getId();
method public java.lang.String getKey();
method public android.app.Notification getNotification();
- method public android.app.NotificationChannel getNotificationChannel();
method public java.lang.String getOverrideGroupKey();
method public java.lang.String getPackageName();
method public long getPostTime();
@@ -38611,6 +38611,7 @@
method public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(java.lang.String);
method public java.lang.String iccTransmitApduBasicChannel(int, int, int, int, int, java.lang.String);
method public java.lang.String iccTransmitApduLogicalChannel(int, int, int, int, int, int, java.lang.String);
+ method public boolean isConcurrentVoiceAndDataAllowed();
method public boolean isHearingAidCompatibilitySupported();
method public boolean isNetworkRoaming();
method public boolean isSmsCapable();
@@ -40051,22 +40052,6 @@
method public android.text.StaticLayout.Builder setTextDirection(android.text.TextDirectionHeuristic);
}
- public abstract interface TextAssistant {
- method public abstract void addLinks(android.text.Spannable, int);
- method public abstract android.text.TextSelection suggestSelection(java.lang.CharSequence, int, int);
- }
-
- public class TextClassification {
- ctor public TextClassification();
- method public java.util.Map<java.lang.String, java.lang.Float> getTypeConfidence();
- }
-
- public final class TextClassificationManager implements android.text.TextAssistant {
- method public void addLinks(android.text.Spannable, int);
- method public java.util.List<android.text.TextLanguage> detectLanguages(java.lang.CharSequence);
- method public android.text.TextSelection suggestSelection(java.lang.CharSequence, int, int);
- }
-
public abstract interface TextDirectionHeuristic {
method public abstract boolean isRtl(char[], int, int);
method public abstract boolean isRtl(java.lang.CharSequence, int, int);
@@ -40082,13 +40067,6 @@
field public static final android.text.TextDirectionHeuristic RTL;
}
- public final class TextLanguage {
- ctor public TextLanguage(int, int, java.util.Map<java.lang.String, java.lang.Float>);
- method public int getEndIndex();
- method public java.util.Map<java.lang.String, java.lang.Float> getLanguageConfidence();
- method public int getStartIndex();
- }
-
public class TextPaint extends android.graphics.Paint {
ctor public TextPaint();
ctor public TextPaint(int);
@@ -40101,13 +40079,6 @@
field public int linkColor;
}
- public class TextSelection {
- ctor public TextSelection();
- method public int getSelectionEndIndex();
- method public int getSelectionStartIndex();
- method public android.text.TextClassification getTextClassification();
- }
-
public class TextUtils {
method public static deprecated java.lang.CharSequence commaEllipsize(java.lang.CharSequence, android.text.TextPaint, float, java.lang.String, java.lang.String);
method public static java.lang.CharSequence concat(java.lang.CharSequence...);
@@ -46793,6 +46764,83 @@
}
+package android.view.textclassifier {
+
+ public abstract interface LinksInfo {
+ method public abstract boolean apply(java.lang.CharSequence);
+ }
+
+ public final class TextClassificationManager {
+ method public java.util.List<android.view.textclassifier.TextLanguage> detectLanguages(java.lang.CharSequence);
+ method public android.view.textclassifier.TextClassifier getDefaultTextClassifier();
+ }
+
+ public final class TextClassificationResult {
+ method public float getConfidenceScore(java.lang.String);
+ method public java.lang.String getEntity(int);
+ method public int getEntityCount();
+ method public android.graphics.drawable.Drawable getIcon();
+ method public android.content.Intent getIntent();
+ method public java.lang.CharSequence getLabel();
+ method public android.view.View.OnClickListener getOnClickListener();
+ method public java.lang.String getText();
+ }
+
+ public static final class TextClassificationResult.Builder {
+ ctor public TextClassificationResult.Builder();
+ method public android.view.textclassifier.TextClassificationResult build();
+ method public android.view.textclassifier.TextClassificationResult.Builder setEntityType(java.lang.String, float);
+ method public android.view.textclassifier.TextClassificationResult.Builder setIcon(android.graphics.drawable.Drawable);
+ method public android.view.textclassifier.TextClassificationResult.Builder setIntent(android.content.Intent);
+ method public android.view.textclassifier.TextClassificationResult.Builder setLabel(java.lang.String);
+ method public android.view.textclassifier.TextClassificationResult.Builder setOnClickListener(android.view.View.OnClickListener);
+ method public android.view.textclassifier.TextClassificationResult.Builder setText(java.lang.String);
+ }
+
+ public abstract interface TextClassifier {
+ method public abstract android.view.textclassifier.LinksInfo getLinks(java.lang.CharSequence, int);
+ method public abstract android.view.textclassifier.TextClassificationResult getTextClassificationResult(java.lang.CharSequence, int, int);
+ method public abstract android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int);
+ field public static final android.view.textclassifier.TextClassifier NO_OP;
+ field public static final java.lang.String TYPE_ADDRESS = "address";
+ field public static final java.lang.String TYPE_EMAIL = "email";
+ field public static final java.lang.String TYPE_OTHER = "other";
+ field public static final java.lang.String TYPE_PHONE = "phone";
+ }
+
+ public static abstract class TextClassifier.EntityType implements java.lang.annotation.Annotation {
+ }
+
+ public final class TextLanguage {
+ method public float getConfidenceScore(java.util.Locale);
+ method public int getEndIndex();
+ method public java.util.Locale getLanguage(int);
+ method public int getLanguageCount();
+ method public int getStartIndex();
+ }
+
+ public static final class TextLanguage.Builder {
+ ctor public TextLanguage.Builder(int, int);
+ method public android.view.textclassifier.TextLanguage build();
+ method public android.view.textclassifier.TextLanguage.Builder setLanguage(java.util.Locale, float);
+ }
+
+ public final class TextSelection {
+ method public float getConfidenceScore(java.lang.String);
+ method public java.lang.String getEntity(int);
+ method public int getEntityCount();
+ method public int getSelectionEndIndex();
+ method public int getSelectionStartIndex();
+ }
+
+ public static final class TextSelection.Builder {
+ ctor public TextSelection.Builder(int, int);
+ method public android.view.textclassifier.TextSelection build();
+ method public android.view.textclassifier.TextSelection.Builder setEntityType(java.lang.String, float);
+ }
+
+}
+
package android.view.textservice {
public final class SentenceSuggestionsInfo implements android.os.Parcelable {
@@ -49889,7 +49937,7 @@
method public float getShadowRadius();
method public final boolean getShowSoftInputOnFocus();
method public java.lang.CharSequence getText();
- method public android.text.TextAssistant getTextAssistant();
+ method public android.view.textclassifier.TextClassifier getTextClassifier();
method public final android.content.res.ColorStateList getTextColors();
method public java.util.Locale getTextLocale();
method public android.os.LocaleList getTextLocales();
@@ -50005,7 +50053,7 @@
method public final void setText(int, android.widget.TextView.BufferType);
method public void setTextAppearance(int);
method public deprecated void setTextAppearance(android.content.Context, int);
- method public void setTextAssistant(android.text.TextAssistant);
+ method public void setTextClassifier(android.view.textclassifier.TextClassifier);
method public void setTextColor(int);
method public void setTextColor(android.content.res.ColorStateList);
method public void setTextIsSelectable(boolean);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 556d7ad..a9d1cf6 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -78,8 +78,6 @@
import android.service.autofill.IAutoFillAppCallback;
import android.text.Selection;
import android.text.SpannableStringBuilder;
-import android.text.TextAssistant;
-import android.text.TextClassificationManager;
import android.text.TextUtils;
import android.text.method.TextKeyListener;
import android.transition.Scene;
@@ -792,8 +790,6 @@
private VoiceInteractor mVoiceInteractor;
- private TextAssistant mTextAssistant;
-
private CharSequence mTitle;
private int mTitleColor = 0;
@@ -1398,24 +1394,6 @@
}
/**
- * Sets the default {@link TextAssistant} for {@link android.widget.TextView}s in this Activity.
- */
- public void setTextAssistant(TextAssistant textAssistant) {
- mTextAssistant = textAssistant;
- }
-
- /**
- * Returns the default {@link TextAssistant} for {@link android.widget.TextView}s
- * in this Activity.
- */
- public TextAssistant getTextAssistant() {
- if (mTextAssistant != null) {
- return mTextAssistant;
- }
- return getSystemService(TextClassificationManager.class);
- }
-
- /**
* This is called for activities that set launchMode to "singleTop" in
* their package, or if a client used the {@link Intent#FLAG_ACTIVITY_SINGLE_TOP}
* flag when calling {@link #startActivity}. In either case, when the
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index f909af0..d674bfe 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -47,6 +47,8 @@
in Notification notification, inout int[] idReceived, int userId);
void cancelNotificationWithTag(String pkg, String tag, int id, int userId);
+ void setShowBadge(String pkg, int uid, boolean showBadge);
+ boolean canShowBadge(String pkg, int uid);
void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled);
boolean areNotificationsEnabledForPackage(String pkg, int uid);
boolean areNotificationsEnabled(String pkg);
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 6793c90..c0bf0c4 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -24,25 +24,25 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.os.Binder;
import android.os.Handler;
-import android.os.Looper;
-import android.os.PowerManager;
-import android.os.RemoteException;
import android.os.IBinder;
-import android.os.IUserManager;
+import android.os.Looper;
+import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.os.UserHandle;
import android.util.Log;
-import android.view.IWindowManager;
import android.view.IOnKeyguardExitResult;
-import android.view.WindowManager;
+import android.view.IWindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.WindowManagerGlobal;
import com.android.internal.policy.IKeyguardDismissCallback;
+import java.util.List;
+
/**
* Class that can be used to lock and unlock the keyboard. Get an instance of this
* class by calling {@link android.content.Context#getSystemService(java.lang.String)}
@@ -100,12 +100,9 @@
Intent intent = new Intent(ACTION_CONFIRM_DEVICE_CREDENTIAL);
intent.putExtra(EXTRA_TITLE, title);
intent.putExtra(EXTRA_DESCRIPTION, description);
- if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
- intent.setPackage("com.google.android.apps.wearable.settings");
- } else {
- // For security reasons, only allow this to come from system settings.
- intent.setPackage("com.android.settings");
- }
+
+ // explicitly set the package for security
+ intent.setPackage(getSettingsPackageForIntent(intent));
return intent;
}
@@ -126,15 +123,23 @@
intent.putExtra(EXTRA_TITLE, title);
intent.putExtra(EXTRA_DESCRIPTION, description);
intent.putExtra(Intent.EXTRA_USER_ID, userId);
- if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
- intent.setPackage("com.google.android.apps.wearable.settings");
- } else {
- // For security reasons, only allow this to come from system settings.
- intent.setPackage("com.android.settings");
- }
+
+ // explicitly set the package for security
+ intent.setPackage(getSettingsPackageForIntent(intent));
+
return intent;
}
+ private String getSettingsPackageForIntent(Intent intent) {
+ List<ResolveInfo> resolveInfos = mContext.getPackageManager()
+ .queryIntentActivities(intent, PackageManager.MATCH_SYSTEM_ONLY);
+ for (int i = 0; i < resolveInfos.size(); i++) {
+ return resolveInfos.get(i).activityInfo.packageName;
+ }
+
+ return "com.android.settings";
+ }
+
/**
* @deprecated Use {@link LayoutParams#FLAG_DISMISS_KEYGUARD}
* and/or {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 601dfce..82917d2 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1051,7 +1051,7 @@
private final Bundle mExtras;
private Icon mIcon;
private final RemoteInput[] mRemoteInputs;
- private boolean mAllowGeneratedReplies = false;
+ private boolean mAllowGeneratedReplies = true;
/**
* Small icon representing the action.
@@ -1093,7 +1093,7 @@
*/
@Deprecated
public Action(int icon, CharSequence title, PendingIntent intent) {
- this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, false);
+ this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, true);
}
/** Keep in sync with {@link Notification.Action.Builder#Builder(Action)}! */
@@ -1166,7 +1166,7 @@
private final Icon mIcon;
private final CharSequence mTitle;
private final PendingIntent mIntent;
- private boolean mAllowGeneratedReplies;
+ private boolean mAllowGeneratedReplies = true;
private final Bundle mExtras;
private ArrayList<RemoteInput> mRemoteInputs;
@@ -1188,7 +1188,7 @@
* @param intent the {@link PendingIntent} to fire when users trigger this action
*/
public Builder(Icon icon, CharSequence title, PendingIntent intent) {
- this(icon, title, intent, new Bundle(), null, false);
+ this(icon, title, intent, new Bundle(), null, true);
}
/**
@@ -1260,7 +1260,7 @@
* @param allowGeneratedReplies {@code true} to allow generated replies, {@code false}
* otherwise
* @return this object for method chaining
- * The default value is {@code false}
+ * The default value is {@code true}
*/
public Builder setAllowGeneratedReplies(boolean allowGeneratedReplies) {
mAllowGeneratedReplies = allowGeneratedReplies;
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 56ef791..be5f80a 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -122,6 +122,7 @@
private static final int DEFAULT_IMPORTANCE =
NotificationManager.IMPORTANCE_UNSPECIFIED;
private static final boolean DEFAULT_DELETED = false;
+ private static final boolean DEFAULT_SHOW_BADGE = true;
private final String mId;
private CharSequence mName;
@@ -133,7 +134,7 @@
private long[] mVibration;
private int mUserLockedFields;
private boolean mVibrationEnabled;
- private boolean mShowBadge;
+ private boolean mShowBadge = DEFAULT_SHOW_BADGE;
private boolean mDeleted = DEFAULT_DELETED;
/**
@@ -368,6 +369,8 @@
/**
* Returns whether notifications posted to this channel can appear as badges in a Launcher
* application.
+ *
+ * Note that badging may be disabled for other reasons.
*/
public boolean canShowBadge() {
return mShowBadge;
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index a37f22b..5d8909c 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -119,7 +119,6 @@
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.FontManager;
-import android.text.TextClassificationManager;
import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
@@ -128,6 +127,7 @@
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.CaptioningManager;
import android.view.inputmethod.InputMethodManager;
+import android.view.textclassifier.TextClassificationManager;
import android.view.textservice.TextServicesManager;
import com.android.internal.app.IAppOpsService;
@@ -228,10 +228,10 @@
}});
registerService(Context.TEXT_CLASSIFICATION_SERVICE, TextClassificationManager.class,
- new StaticServiceFetcher<TextClassificationManager>() {
+ new CachedServiceFetcher<TextClassificationManager>() {
@Override
- public TextClassificationManager createService() {
- return new TextClassificationManager();
+ public TextClassificationManager createService(ContextImpl ctx) {
+ return new TextClassificationManager(ctx);
}});
registerService(Context.CLIPBOARD_SERVICE, ClipboardManager.class,
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index f41d7f2..38e6fbe 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -34,9 +34,7 @@
import android.annotation.UserIdInt;
import android.app.IApplicationThread;
import android.app.IServiceConnection;
-import android.app.LoadedApk;
import android.app.Notification;
-import android.app.admin.DevicePolicyManager;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
@@ -64,6 +62,7 @@
import android.view.DisplayAdjustments;
import android.view.ViewDebug;
import android.view.WindowManager;
+import android.view.textclassifier.TextClassificationManager;
import java.io.File;
import java.io.FileInputStream;
@@ -3348,10 +3347,10 @@
/**
* Use with {@link #getSystemService} to retrieve a
- * {@link android.text.TextClassificationManager} for text classification services.
+ * {@link TextClassificationManager} for text classification services.
*
* @see #getSystemService
- * @see android.text.TextClassificationManager
+ * @see TextClassificationManager
*/
public static final String TEXT_CLASSIFICATION_SERVICE = "textclassification";
diff --git a/core/java/android/content/pm/ComponentInfo.java b/core/java/android/content/pm/ComponentInfo.java
index 5cd15dd..b091d7e 100644
--- a/core/java/android/content/pm/ComponentInfo.java
+++ b/core/java/android/content/pm/ComponentInfo.java
@@ -72,6 +72,11 @@
*/
public boolean directBootAware = false;
+ /**
+ * The name of the split that contains the code for this component.
+ */
+ public String splitName;
+
/** @removed */
@Deprecated
public boolean encryptionAware = false;
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index d8d7abe..ca3011e 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -3899,6 +3899,9 @@
a.info.taskAffinity = buildTaskAffinityName(owner.applicationInfo.packageName,
owner.applicationInfo.taskAffinity, str, outError);
+ a.info.splitName =
+ sa.getNonConfigurationString(R.styleable.AndroidManifestActivity_splitName, 0);
+
a.info.flags = 0;
if (sa.getBoolean(
R.styleable.AndroidManifestActivity_multiprocess, false)) {
@@ -4520,6 +4523,9 @@
com.android.internal.R.styleable.AndroidManifestProvider_initOrder,
0);
+ p.info.splitName =
+ sa.getNonConfigurationString(R.styleable.AndroidManifestProvider_splitName, 0);
+
p.info.flags = 0;
if (sa.getBoolean(
@@ -4816,6 +4822,9 @@
s.info.permission = str.length() > 0 ? str.toString().intern() : null;
}
+ s.info.splitName =
+ sa.getNonConfigurationString(R.styleable.AndroidManifestService_splitName, 0);
+
s.info.flags = 0;
if (sa.getBoolean(
com.android.internal.R.styleable.AndroidManifestService_stopWithTask,
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 694837e..d930689 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1166,11 +1166,12 @@
// System specified group key.
private String mOverrideGroupKey;
// Notification assistant channel override.
- private NotificationChannel mOverrideChannel;
+ private NotificationChannel mChannel;
// Notification assistant people override.
private ArrayList<String> mOverridePeople;
// Notification assistant snooze criteria.
private ArrayList<SnoozeCriterion> mSnoozeCriteria;
+ private boolean mShowBadge;
public Ranking() {}
@@ -1200,7 +1201,7 @@
}
/**
- * Returns the user specificed visibility for the package that posted
+ * Returns the user specified visibility for the package that posted
* this notification, or
* {@link NotificationListenerService.Ranking#VISIBILITY_NO_OVERRIDE} if
* no such preference has been expressed.
@@ -1233,7 +1234,7 @@
* Returns the importance of the notification, which dictates its
* modes of presentation, see: {@link NotificationManager#IMPORTANCE_DEFAULT}, etc.
*
- * @return the rank of the notification
+ * @return the importance of the notification
*/
public @NotificationManager.Importance int getImportance() {
return mImportance;
@@ -1258,12 +1259,11 @@
}
/**
- * If the {@link NotificationAssistantService} has overridden the channel this notification
- * was posted to, then this will not match the channel provided by the posting application
- * and this should be used to determine the interruptiveness of the notification instead.
+ * Returns the notification channel this notification was posted to, which dictates
+ * notification behavior and presentation.
*/
public NotificationChannel getChannel() {
- return mOverrideChannel;
+ return mChannel;
}
/**
@@ -1283,11 +1283,20 @@
return mSnoozeCriteria;
}
+ /**
+ * Returns whether this notification can be displayed as a badge.
+ *
+ * @return true if the notification can be displayed as a badge, false otherwise.
+ */
+ public boolean canShowBadge() {
+ return mShowBadge;
+ }
+
private void populate(String key, int rank, boolean matchesInterruptionFilter,
int visibilityOverride, int suppressedVisualEffects, int importance,
CharSequence explanation, String overrideGroupKey,
- NotificationChannel overrideChannel, ArrayList<String> overridePeople,
- ArrayList<SnoozeCriterion> snoozeCriteria) {
+ NotificationChannel channel, ArrayList<String> overridePeople,
+ ArrayList<SnoozeCriterion> snoozeCriteria, boolean showBadge) {
mKey = key;
mRank = rank;
mIsAmbient = importance < NotificationManager.IMPORTANCE_LOW;
@@ -1297,9 +1306,10 @@
mImportance = importance;
mImportanceExplanation = explanation;
mOverrideGroupKey = overrideGroupKey;
- mOverrideChannel = overrideChannel;
+ mChannel = channel;
mOverridePeople = overridePeople;
mSnoozeCriteria = snoozeCriteria;
+ mShowBadge = showBadge;
}
/**
@@ -1343,9 +1353,10 @@
private ArrayMap<String, Integer> mImportance;
private ArrayMap<String, String> mImportanceExplanation;
private ArrayMap<String, String> mOverrideGroupKeys;
- private ArrayMap<String, NotificationChannel> mOverrideChannels;
+ private ArrayMap<String, NotificationChannel> mChannels;
private ArrayMap<String, ArrayList<String>> mOverridePeople;
private ArrayMap<String, ArrayList<SnoozeCriterion>> mSnoozeCriteria;
+ private ArrayMap<String, Boolean> mShowBadge;
private RankingMap(NotificationRankingUpdate rankingUpdate) {
mRankingUpdate = rankingUpdate;
@@ -1373,7 +1384,8 @@
outRanking.populate(key, rank, !isIntercepted(key),
getVisibilityOverride(key), getSuppressedVisualEffects(key),
getImportance(key), getImportanceExplanation(key), getOverrideGroupKey(key),
- getOverrideChannel(key), getOverridePeople(key), getSnoozeCriteria(key));
+ getChannel(key), getOverridePeople(key), getSnoozeCriteria(key),
+ getShowBadge(key));
return rank >= 0;
}
@@ -1453,13 +1465,13 @@
return mOverrideGroupKeys.get(key);
}
- private NotificationChannel getOverrideChannel(String key) {
+ private NotificationChannel getChannel(String key) {
synchronized (this) {
- if (mOverrideChannels == null) {
- buildOverrideChannelsLocked();
+ if (mChannels == null) {
+ buildChannelsLocked();
}
}
- return mOverrideChannels.get(key);
+ return mChannels.get(key);
}
private ArrayList<String> getOverridePeople(String key) {
@@ -1480,6 +1492,16 @@
return mSnoozeCriteria.get(key);
}
+ private boolean getShowBadge(String key) {
+ synchronized (this) {
+ if (mShowBadge == null) {
+ buildShowBadgeLocked();
+ }
+ }
+ Boolean showBadge = mShowBadge.get(key);
+ return showBadge == null ? false : showBadge.booleanValue();
+ }
+
// Locked by 'this'
private void buildRanksLocked() {
String[] orderedKeys = mRankingUpdate.getOrderedKeys();
@@ -1544,11 +1566,11 @@
}
// Locked by 'this'
- private void buildOverrideChannelsLocked() {
- Bundle overrideChannels = mRankingUpdate.getOverrideChannels();
- mOverrideChannels = new ArrayMap<>(overrideChannels.size());
- for (String key : overrideChannels.keySet()) {
- mOverrideChannels.put(key, overrideChannels.getParcelable(key));
+ private void buildChannelsLocked() {
+ Bundle channels = mRankingUpdate.getChannels();
+ mChannels = new ArrayMap<>(channels.size());
+ for (String key : channels.keySet()) {
+ mChannels.put(key, channels.getParcelable(key));
}
}
@@ -1570,6 +1592,15 @@
}
}
+ // Locked by 'this'
+ private void buildShowBadgeLocked() {
+ Bundle showBadge = mRankingUpdate.getShowBadge();
+ mShowBadge = new ArrayMap<>(showBadge.size());
+ for (String key : showBadge.keySet()) {
+ mShowBadge.put(key, showBadge.getBoolean(key));
+ }
+ }
+
// ----------- Parcelable
@Override
diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java
index a2cdeff..326b212 100644
--- a/core/java/android/service/notification/NotificationRankingUpdate.java
+++ b/core/java/android/service/notification/NotificationRankingUpdate.java
@@ -31,14 +31,16 @@
private final int[] mImportance;
private final Bundle mImportanceExplanation;
private final Bundle mOverrideGroupKeys;
- private final Bundle mOverrideChannels;
+ private final Bundle mChannels;
private final Bundle mOverridePeople;
private final Bundle mSnoozeCriteria;
+ private final Bundle mShowBadge;
public NotificationRankingUpdate(String[] keys, String[] interceptedKeys,
Bundle visibilityOverrides, Bundle suppressedVisualEffects,
int[] importance, Bundle explanation, Bundle overrideGroupKeys,
- Bundle overrideChannels, Bundle overridePeople, Bundle snoozeCriteria) {
+ Bundle channels, Bundle overridePeople, Bundle snoozeCriteria,
+ Bundle showBadge) {
mKeys = keys;
mInterceptedKeys = interceptedKeys;
mVisibilityOverrides = visibilityOverrides;
@@ -46,9 +48,10 @@
mImportance = importance;
mImportanceExplanation = explanation;
mOverrideGroupKeys = overrideGroupKeys;
- mOverrideChannels = overrideChannels;
+ mChannels = channels;
mOverridePeople = overridePeople;
mSnoozeCriteria = snoozeCriteria;
+ mShowBadge = showBadge;
}
public NotificationRankingUpdate(Parcel in) {
@@ -60,9 +63,10 @@
in.readIntArray(mImportance);
mImportanceExplanation = in.readBundle();
mOverrideGroupKeys = in.readBundle();
- mOverrideChannels = in.readBundle();
+ mChannels = in.readBundle();
mOverridePeople = in.readBundle();
mSnoozeCriteria = in.readBundle();
+ mShowBadge = in.readBundle();
}
@Override
@@ -79,9 +83,10 @@
out.writeIntArray(mImportance);
out.writeBundle(mImportanceExplanation);
out.writeBundle(mOverrideGroupKeys);
- out.writeBundle(mOverrideChannels);
+ out.writeBundle(mChannels);
out.writeBundle(mOverridePeople);
out.writeBundle(mSnoozeCriteria);
+ out.writeBundle(mShowBadge);
}
public static final Parcelable.Creator<NotificationRankingUpdate> CREATOR
@@ -123,8 +128,8 @@
return mOverrideGroupKeys;
}
- public Bundle getOverrideChannels() {
- return mOverrideChannels;
+ public Bundle getChannels() {
+ return mChannels;
}
public Bundle getOverridePeople() {
@@ -134,4 +139,8 @@
public Bundle getSnoozeCriteria() {
return mSnoozeCriteria;
}
+
+ public Bundle getShowBadge() {
+ return mShowBadge;
+ }
}
diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java
index 6276af3..85baf4e 100644
--- a/core/java/android/service/notification/StatusBarNotification.java
+++ b/core/java/android/service/notification/StatusBarNotification.java
@@ -43,21 +43,18 @@
private final Notification notification;
private final UserHandle user;
private final long postTime;
- private final NotificationChannel channel;
private Context mContext; // used for inflation & icon expansion
/** @hide */
- public StatusBarNotification(String pkg, String opPkg, NotificationChannel channel, int id,
+ public StatusBarNotification(String pkg, String opPkg, int id,
String tag, int uid, int initialPid, Notification notification, UserHandle user,
String overrideGroupKey, long postTime) {
if (pkg == null) throw new NullPointerException();
if (notification == null) throw new NullPointerException();
- if (channel == null) throw new IllegalArgumentException();
this.pkg = pkg;
this.opPkg = opPkg;
- this.channel = channel;
this.id = id;
this.tag = tag;
this.uid = uid;
@@ -88,7 +85,6 @@
this.postTime = postTime;
this.key = key();
this.groupKey = groupKey();
- this.channel = null;
}
public StatusBarNotification(Parcel in) {
@@ -112,7 +108,6 @@
}
this.key = key();
this.groupKey = groupKey();
- this.channel = NotificationChannel.CREATOR.createFromParcel(in);
}
private String key() {
@@ -182,7 +177,6 @@
} else {
out.writeInt(0);
}
- this.channel.writeToParcel(out, flags);
}
public int describeContents() {
@@ -209,14 +203,14 @@
public StatusBarNotification cloneLight() {
final Notification no = new Notification();
this.notification.cloneInto(no, false); // light copy
- return new StatusBarNotification(this.pkg, this.opPkg, this.channel,
+ return new StatusBarNotification(this.pkg, this.opPkg,
this.id, this.tag, this.uid, this.initialPid,
no, this.user, this.overrideGroupKey, this.postTime);
}
@Override
public StatusBarNotification clone() {
- return new StatusBarNotification(this.pkg, this.opPkg, this.channel,
+ return new StatusBarNotification(this.pkg, this.opPkg,
this.id, this.tag, this.uid, this.initialPid,
this.notification.clone(), this.user, this.overrideGroupKey, this.postTime);
}
@@ -336,13 +330,6 @@
}
/**
- * Returns the channel this notification was posted to.
- */
- public NotificationChannel getNotificationChannel() {
- return channel;
- }
-
- /**
* @hide
*/
public Context getPackageContext(Context context) {
diff --git a/core/java/android/text/LangId.java b/core/java/android/text/LangId.java
new file mode 100644
index 0000000..ed6e909
--- /dev/null
+++ b/core/java/android/text/LangId.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 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.text;
+
+/**
+ * Java wrapper for LangId native library interface.
+ * This class is used to detect languages in text.
+ * @hide
+ */
+public final class LangId {
+ // TODO: Move this to android.view.textclassifier and make it package-private.
+ // We'll have to update the native library code to do this.
+
+ static {
+ System.loadLibrary("smart-selection_jni");
+ }
+
+ private final long mModelPtr;
+
+ /**
+ * Creates a new instance of LangId predictor, using the provided model image.
+ */
+ public LangId(int fd) {
+ mModelPtr = nativeNew(fd);
+ }
+
+ /**
+ * Detects the language for given text.
+ */
+ public String findLanguage(String text) {
+ return nativeFindLanguage(mModelPtr, text);
+ }
+
+ /**
+ * Frees up the allocated memory.
+ */
+ public void close() {
+ nativeClose(mModelPtr);
+ }
+
+ private static native long nativeNew(int fd);
+
+ private static native String nativeFindLanguage(long context, String text);
+
+ private static native void nativeClose(long context);
+}
+
diff --git a/core/java/android/text/SmartSelection.java b/core/java/android/text/SmartSelection.java
new file mode 100644
index 0000000..97ef514
--- /dev/null
+++ b/core/java/android/text/SmartSelection.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2017 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.text;
+
+/**
+ * Java wrapper for SmartSelection native library interface.
+ * This library is used for detecting entities in text.
+ * @hide
+ */
+public final class SmartSelection {
+ // TODO: Move this to android.view.textclassifier and make it package-private.
+ // We'll have to update the native library code to do this.
+
+ static {
+ System.loadLibrary("smart-selection_jni");
+ }
+
+ private final long mCtx;
+
+ /**
+ * Creates a new instance of SmartSelect predictor, using the provided model image,
+ * given as a file descriptor.
+ */
+ public SmartSelection(int fd) {
+ mCtx = nativeNew(fd);
+ }
+
+ /**
+ * Given a string context and current selection, computes the SmartSelection suggestion.
+ *
+ * The begin and end are character indices into the context UTF8 string. selectionBegin is the
+ * character index where the selection begins, and selectionEnd is the index of one character
+ * past the selection span.
+ *
+ * The return value is an array of two ints: suggested selection beginning and end, with the
+ * same semantics as the input selectionBeginning and selectionEnd.
+ */
+ public int[] suggest(String context, int selectionBegin, int selectionEnd) {
+ return nativeSuggest(mCtx, context, selectionBegin, selectionEnd);
+ }
+
+ /**
+ * Given a string context and current selection, classifies the type of the selected text.
+ *
+ * The begin and end params are character indices in the context string.
+ *
+ * Returns the type of the selection, e.g. "email", "address", "phone".
+ */
+ public String classifyText(String context, int selectionBegin, int selectionEnd) {
+ return nativeClassifyText(mCtx, context, selectionBegin, selectionEnd);
+ }
+
+ /**
+ * Frees up the allocated memory.
+ */
+ public void close() {
+ nativeClose(mCtx);
+ }
+
+ private static native long nativeNew(int fd);
+
+ private static native int[] nativeSuggest(
+ long context, String text, int selectionBegin, int selectionEnd);
+
+ private static native String nativeClassifyText(
+ long context, String text, int selectionBegin, int selectionEnd);
+
+ private static native void nativeClose(long context);
+}
+
diff --git a/core/java/android/text/TextAssistant.java b/core/java/android/text/TextAssistant.java
deleted file mode 100644
index b044981..0000000
--- a/core/java/android/text/TextAssistant.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2016 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.text;
-
-/**
- * Interface for providing text assistant features.
- */
-public interface TextAssistant {
-
- /**
- * NO_OP TextAssistant. This will act as the default TextAssistant until we implement a
- * TextClassificationManager.
- * @hide
- */
- TextAssistant NO_OP = new TextAssistant() {
-
- private final TextSelection mTextSelection = new TextSelection();
-
- @Override
- public TextSelection suggestSelection(
- CharSequence text, int selectionStartIndex, int selectionEndIndex) {
- mTextSelection.mStartIndex = selectionStartIndex;
- mTextSelection.mEndIndex = selectionEndIndex;
- return mTextSelection;
- }
-
- @Override
- public void addLinks(Spannable text, int linkMask) {}
- };
-
- /**
- * Returns suggested text selection indices, recognized types and their associated confidence
- * scores. The selections are ordered from highest to lowest scoring.
- */
- TextSelection suggestSelection(
- CharSequence text, int selectionStartIndex, int selectionEndIndex);
-
- /**
- * Adds assistance clickable spans to the provided text.
- */
- void addLinks(Spannable text, int linkMask);
-}
diff --git a/core/java/android/text/TextClassification.java b/core/java/android/text/TextClassification.java
deleted file mode 100644
index bb226da..0000000
--- a/core/java/android/text/TextClassification.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2016 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.text;
-
-import java.util.Collections;
-import java.util.Map;
-
-/**
- * Information about entities that a specific piece of text is classified as.
- */
-public class TextClassification {
-
- /** @hide */
- public static final TextClassification NO_OP = new TextClassification();
-
- private Map<String, Float> mTypeConfidence = Collections.unmodifiableMap(Collections.EMPTY_MAP);
-
- /**
- * Returns a map of text classification types to their respective confidence scores.
- * The scores range from 0 (low confidence) to 1 (high confidence). The items are ordered from
- * high scoring items to low scoring items.
- */
- public Map<String, Float> getTypeConfidence() {
- return mTypeConfidence;
- }
-}
diff --git a/core/java/android/text/TextClassificationManager.java b/core/java/android/text/TextClassificationManager.java
deleted file mode 100644
index d4548f0..0000000
--- a/core/java/android/text/TextClassificationManager.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2016 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.text;
-
-import android.annotation.NonNull;
-
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Interface to the text classification service.
- * This class uses machine learning techniques to infer things about text.
- * Unless otherwise stated, methods of this class are blocking operations and should most likely not
- * be called on the UI thread.
- *
- * <p> You do not instantiate this class directly; instead, retrieve it through
- * {@link android.content.Context#getSystemService}.
- *
- * The TextClassificationManager serves as the default TextAssistant if none has been set.
- * @see android.app.Activity#setTextAssistant(TextAssistant).
- */
-public final class TextClassificationManager implements TextAssistant {
- // TODO: Consider not making this class implement TextAssistant.
-
- /** @hide */
- public TextClassificationManager() {}
-
- /**
- * Returns information containing languages that were detected in the provided text.
- * This is a blocking operation and should most likely not be called on the UI thread.
- */
- public List<TextLanguage> detectLanguages(@NonNull CharSequence text) {
- // TODO: Implement this using the cld3 library.
- return Collections.emptyList();
- }
-
- @Override
- public TextSelection suggestSelection(
- @NonNull CharSequence text, int selectionStartIndex, int selectionEndIndex) {
- // TODO: Implement.
- return TextAssistant.NO_OP.suggestSelection(text, selectionStartIndex, selectionEndIndex);
- }
-
- @Override
- public void addLinks(@NonNull Spannable text, int linkMask) {
- // TODO: Implement.
- }
-}
diff --git a/core/java/android/text/TextLanguage.java b/core/java/android/text/TextLanguage.java
deleted file mode 100644
index eb834f1..0000000
--- a/core/java/android/text/TextLanguage.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2016 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.text;
-
-import android.annotation.NonNull;
-
-import com.android.internal.util.Preconditions;
-
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-/**
- * Specifies detected languages for a section of text indicated by a start and end index.
- */
-public final class TextLanguage {
-
- private final int mStartIndex;
- private final int mEndIndex;
- private final Map<String, Float> mLanguageConfidence;
-
- /**
- * Initializes a TextLanguage object.
- *
- * @param startIndex the start index of the detected languages in the text provided to generate
- * this object.
- * @param endIndex the end index of the detected languages in the text provided to generate this
- * object.
- * @param languageConfidence a map of detected language to confidence score. The language string
- * is a BCP-47 language tag.
- * @throws NullPointerException if languageConfidence is null or contains a null key or value.
- */
- public TextLanguage(int startIndex, int endIndex,
- @NonNull Map<String, Float> languageConfidence) {
- mStartIndex = startIndex;
- mEndIndex = endIndex;
-
- Map<String, Float> map = new LinkedHashMap<>();
- Preconditions.checkNotNull(languageConfidence).entrySet().stream()
- .sorted(Map.Entry.comparingByValue())
- .forEach(entry -> map.put(
- Preconditions.checkNotNull(entry.getKey()),
- Preconditions.checkNotNull(entry.getValue())));
- mLanguageConfidence = Collections.unmodifiableMap(map);
- }
-
- /**
- * Returns the start index of the detected languages in the text provided to generate this
- * object.
- */
- public int getStartIndex() {
- return mStartIndex;
- }
-
- /**
- * Returns the end index of the detected languages in the text provided to generate this object.
- */
- public int getEndIndex() {
- return mEndIndex;
- }
-
- /**
- * Returns an unmodifiable map of detected language to confidence score. The map entries are
- * ordered from high confidence score (1) to low confidence score (0). The language string is a
- * BCP-47 language tag.
- */
- @NonNull
- public Map<String, Float> getLanguageConfidence() {
- return mLanguageConfidence;
- }
-}
diff --git a/core/java/android/text/TextSelection.java b/core/java/android/text/TextSelection.java
deleted file mode 100644
index 9400458..0000000
--- a/core/java/android/text/TextSelection.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2016 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.text;
-
-/**
- * Text selection information.
- */
-public class TextSelection {
-
- /** @hide */
- int mStartIndex;
- /** @hide */
- int mEndIndex;
-
- private TextClassification mTextClassification = TextClassification.NO_OP;
-
- /**
- * Returns the start index of the text selection.
- */
- public int getSelectionStartIndex() {
- return mStartIndex;
- }
-
- /**
- * Returns the end index of the text selection.
- */
- public int getSelectionEndIndex() {
- return mEndIndex;
- }
-
- /**
- * Returns information about what the text selection is classified as.
- */
- public TextClassification getTextClassification() {
- return mTextClassification;
- }
-}
diff --git a/core/java/android/view/textclassifier/EntityConfidence.java b/core/java/android/view/textclassifier/EntityConfidence.java
new file mode 100644
index 0000000..7aab71f
--- /dev/null
+++ b/core/java/android/view/textclassifier/EntityConfidence.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Helper object for setting and getting entity scores for classified text.
+ *
+ * @param <T> the entity type.
+ * @hide
+ */
+final class EntityConfidence<T> {
+
+ private final Map<T, Float> mEntityConfidence = new HashMap<>();
+
+ private final Comparator<T> mEntityComparator = (e1, e2) -> {
+ float score1 = mEntityConfidence.get(e1);
+ float score2 = mEntityConfidence.get(e2);
+ if (score1 > score2) {
+ return 1;
+ }
+ if (score1 < score2) {
+ return -1;
+ }
+ return 0;
+ };
+
+ EntityConfidence() {}
+
+ EntityConfidence(@NonNull EntityConfidence<T> source) {
+ Preconditions.checkNotNull(source);
+ mEntityConfidence.putAll(source.mEntityConfidence);
+ }
+
+ /**
+ * Sets an entity type for the classified text and assigns a confidence score.
+ *
+ * @param confidenceScore a value from 0 (low confidence) to 1 (high confidence).
+ * 0 implies the entity does not exist for the classified text.
+ * Values greater than 1 are clamped to 1.
+ */
+ public void setEntityType(
+ @NonNull T type, @FloatRange(from = 0.0, to = 1.0) float confidenceScore) {
+ Preconditions.checkNotNull(type);
+ if (confidenceScore > 0) {
+ mEntityConfidence.put(type, Math.min(1, confidenceScore));
+ } else {
+ mEntityConfidence.remove(type);
+ }
+ }
+
+ /**
+ * Returns an immutable list of entities found in the classified text ordered from
+ * high confidence to low confidence.
+ */
+ @NonNull
+ public List<T> getEntities() {
+ List<T> entities = new ArrayList<>(mEntityConfidence.size());
+ entities.addAll(mEntityConfidence.keySet());
+ entities.sort(mEntityComparator);
+ return Collections.unmodifiableList(entities);
+ }
+
+ /**
+ * Returns the confidence score for the specified entity. The value ranges from
+ * 0 (low confidence) to 1 (high confidence). 0 indicates that the entity was not found for the
+ * classified text.
+ */
+ @FloatRange(from = 0.0, to = 1.0)
+ public float getConfidenceScore(T entity) {
+ if (mEntityConfidence.containsKey(entity)) {
+ return mEntityConfidence.get(entity);
+ }
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return mEntityConfidence.toString();
+ }
+}
diff --git a/core/java/android/view/textclassifier/LinksInfo.java b/core/java/android/view/textclassifier/LinksInfo.java
new file mode 100644
index 0000000..3acbdc0
--- /dev/null
+++ b/core/java/android/view/textclassifier/LinksInfo.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier;
+
+import android.annotation.NonNull;
+
+/**
+ * Link information that can be applied to text. See: {@link #apply(CharSequence)}.
+ * Typical implementations of this interface will annotate spannable text with e.g
+ * {@link android.text.style.ClickableSpan}s or other annotations.
+ */
+public interface LinksInfo {
+
+ /**
+ * @hide
+ */
+ LinksInfo NO_OP = text -> false;
+
+ /**
+ * Applies link annotations to the specified text.
+ * These annotations are not guaranteed to be applied. For example, the annotations may not be
+ * applied if the text has changed from what it was when the link spec was generated for it.
+ *
+ * @return Whether or not the link annotations were successfully applied.
+ */
+ boolean apply(@NonNull CharSequence text);
+}
diff --git a/core/java/android/view/textclassifier/TextClassificationManager.java b/core/java/android/view/textclassifier/TextClassificationManager.java
new file mode 100644
index 0000000..4673c50
--- /dev/null
+++ b/core/java/android/view/textclassifier/TextClassificationManager.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+import android.text.LangId;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Interface to the text classification service.
+ *
+ * <p>You do not instantiate this class directly; instead, retrieve it through
+ * {@link android.content.Context#getSystemService}.
+ */
+public final class TextClassificationManager {
+
+ private static final String LOG_TAG = "TextClassificationManager";
+
+ private final Context mContext;
+ // TODO: Implement a way to close the file descriptor.
+ private ParcelFileDescriptor mFd;
+ private TextClassifier mDefault;
+ private LangId mLangId;
+
+ /** @hide */
+ public TextClassificationManager(Context context) {
+ mContext = Preconditions.checkNotNull(context);
+ }
+
+ /**
+ * Returns the default text classifier.
+ */
+ public TextClassifier getDefaultTextClassifier() {
+ if (mDefault == null) {
+ try {
+ mFd = ParcelFileDescriptor.open(
+ new File("/etc/assistant/smart-selection.model"),
+ ParcelFileDescriptor.MODE_READ_ONLY);
+ mDefault = new TextClassifierImpl(mContext, mFd);
+ } catch (FileNotFoundException e) {
+ Log.e(LOG_TAG, "Error accessing 'text classifier selection' model file.", e);
+ mDefault = TextClassifier.NO_OP;
+ }
+ }
+ return mDefault;
+ }
+
+ /**
+ * Returns information containing languages that were detected in the provided text.
+ * This is a blocking operation you should avoid calling it on the UI thread.
+ *
+ * @throws IllegalArgumentException if text is null
+ */
+ public List<TextLanguage> detectLanguages(@NonNull CharSequence text) {
+ Preconditions.checkArgument(text != null);
+ try {
+ if (text.length() > 0) {
+ final String language = getLanguageDetector().findLanguage(text.toString());
+ final Locale locale = new Locale.Builder().setLanguageTag(language).build();
+ return Collections.unmodifiableList(Arrays.asList(
+ new TextLanguage.Builder(0, text.length())
+ .setLanguage(locale, 1.0f /* confidence */)
+ .build()));
+ }
+ } catch (Throwable t) {
+ // Avoid throwing from this method. Log the error.
+ Log.e(LOG_TAG, "Error detecting languages for text. Returning empty result.", t);
+ }
+ // Getting here means something went wrong. Return an empty result.
+ return Collections.emptyList();
+ }
+
+ private LangId getLanguageDetector() {
+ if (mLangId == null) {
+ // TODO: Use a file descriptor as soon as we start to depend on a model file
+ // for language detection.
+ mLangId = new LangId(0);
+ }
+ return mLangId;
+ }
+}
diff --git a/core/java/android/view/textclassifier/TextClassificationResult.java b/core/java/android/view/textclassifier/TextClassificationResult.java
new file mode 100644
index 0000000..6af0efb
--- /dev/null
+++ b/core/java/android/view/textclassifier/TextClassificationResult.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier;
+
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.view.View.OnClickListener;
+import android.view.textclassifier.TextClassifier.EntityType;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+
+/**
+ * Information for generating a widget to handle classified text.
+ */
+public final class TextClassificationResult {
+
+ /**
+ * @hide
+ */
+ static final TextClassificationResult EMPTY = new TextClassificationResult.Builder().build();
+
+ @NonNull private final String mText;
+ @Nullable private final Drawable mIcon;
+ @Nullable private final String mLabel;
+ @Nullable private final Intent mIntent;
+ @Nullable private final OnClickListener mOnClickListener;
+ @NonNull private final EntityConfidence<String> mEntityConfidence;
+ @NonNull private final List<String> mEntities;
+
+ private TextClassificationResult(
+ @NonNull String text,
+ Drawable icon,
+ String label,
+ Intent intent,
+ OnClickListener onClickListener,
+ @NonNull EntityConfidence<String> entityConfidence) {
+ mText = text;
+ mIcon = icon;
+ mLabel = label;
+ mIntent = intent;
+ mOnClickListener = onClickListener;
+ mEntityConfidence = new EntityConfidence<>(entityConfidence);
+ mEntities = mEntityConfidence.getEntities();
+ }
+
+ /**
+ * Gets the classified text.
+ */
+ @NonNull
+ public String getText() {
+ return mText;
+ }
+
+ /**
+ * Returns the number of entities found in the classified text.
+ */
+ @IntRange(from = 0)
+ public int getEntityCount() {
+ return mEntities.size();
+ }
+
+ /**
+ * Returns the entity at the specified index. Entities are ordered from high confidence
+ * to low confidence.
+ *
+ * @throws IndexOutOfBoundsException if the specified index is out of range.
+ * @see #getEntityCount() for the number of entities available.
+ */
+ @NonNull
+ public @EntityType String getEntity(int index) {
+ return mEntities.get(index);
+ }
+
+ /**
+ * Returns the confidence score for the specified entity. The value ranges from
+ * 0 (low confidence) to 1 (high confidence). 0 indicates that the entity was not found for the
+ * classified text.
+ */
+ @FloatRange(from = 0.0, to = 1.0)
+ public float getConfidenceScore(@EntityType String entity) {
+ return mEntityConfidence.getConfidenceScore(entity);
+ }
+
+ /**
+ * Returns an icon that may be rendered on a widget used to act on the classified text.
+ */
+ @Nullable
+ public Drawable getIcon() {
+ return mIcon;
+ }
+
+ /**
+ * Returns a label that may be rendered on a widget used to act on the classified text.
+ */
+ @Nullable
+ public CharSequence getLabel() {
+ return mLabel;
+ }
+
+ /**
+ * Returns an intent that may be fired to act on the classified text.
+ */
+ @Nullable
+ public Intent getIntent() {
+ return mIntent;
+ }
+
+ /**
+ * Returns an OnClickListener that may be triggered to act on the classified text.
+ */
+ @Nullable
+ public OnClickListener getOnClickListener() {
+ return mOnClickListener;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("TextClassificationResult {"
+ + "text=%s, entities=%s, label=%s, intent=%s}",
+ mText, mEntityConfidence, mLabel, mIntent);
+ }
+
+ /**
+ * Creates an OnClickListener that starts an activity with the specified intent.
+ *
+ * @throws IllegalArgumentException if context or intent is null
+ * @hide
+ */
+ @NonNull
+ public static OnClickListener createStartActivityOnClick(
+ @NonNull final Context context, @NonNull final Intent intent) {
+ Preconditions.checkArgument(context != null);
+ Preconditions.checkArgument(intent != null);
+ return v -> context.startActivity(intent);
+ }
+
+ /**
+ * Builder for building {@link TextClassificationResult}s.
+ */
+ public static final class Builder {
+
+ @NonNull private String mText;
+ @Nullable private Drawable mIcon;
+ @Nullable private String mLabel;
+ @Nullable private Intent mIntent;
+ @Nullable private OnClickListener mOnClickListener;
+ @NonNull private final EntityConfidence<String> mEntityConfidence =
+ new EntityConfidence<>();
+
+ /**
+ * Sets the classified text.
+ */
+ public Builder setText(@NonNull String text) {
+ mText = Preconditions.checkNotNull(text);
+ return this;
+ }
+
+ /**
+ * Sets an entity type for the classification result and assigns a confidence score.
+ *
+ * @param confidenceScore a value from 0 (low confidence) to 1 (high confidence).
+ * 0 implies the entity does not exist for the classified text.
+ * Values greater than 1 are clamped to 1.
+ */
+ public Builder setEntityType(
+ @NonNull @EntityType String type,
+ @FloatRange(from = 0.0, to = 1.0)float confidenceScore) {
+ mEntityConfidence.setEntityType(type, confidenceScore);
+ return this;
+ }
+
+ /**
+ * Sets an icon that may be rendered on a widget used to act on the classified text.
+ */
+ public Builder setIcon(@Nullable Drawable icon) {
+ mIcon = icon;
+ return this;
+ }
+
+ /**
+ * Sets a label that may be rendered on a widget used to act on the classified text.
+ */
+ public Builder setLabel(@Nullable String label) {
+ mLabel = label;
+ return this;
+ }
+
+ /**
+ * Sets an intent that may be fired to act on the classified text.
+ */
+ public Builder setIntent(@Nullable Intent intent) {
+ mIntent = intent;
+ return this;
+ }
+
+ /**
+ * Sets an OnClickListener that may be triggered to act on the classified text.
+ */
+ public Builder setOnClickListener(@Nullable OnClickListener onClickListener) {
+ mOnClickListener = onClickListener;
+ return this;
+ }
+
+ /**
+ * Builds an returns a {@link TextClassificationResult}.
+ */
+ public TextClassificationResult build() {
+ return new TextClassificationResult(
+ mText, mIcon, mLabel, mIntent, mOnClickListener, mEntityConfidence);
+ }
+ }
+}
diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java
new file mode 100644
index 0000000..b84e2ae
--- /dev/null
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.StringDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Interface for providing text classification related features.
+ *
+ * <p>Unless otherwise stated, methods of this interface are blocking operations and you should
+ * avoid calling them on the UI thread.
+ */
+public interface TextClassifier {
+
+ String TYPE_OTHER = "other";
+ String TYPE_EMAIL = "email";
+ String TYPE_PHONE = "phone";
+ String TYPE_ADDRESS = "address";
+
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef({
+ TYPE_OTHER, TYPE_EMAIL, TYPE_PHONE, TYPE_ADDRESS
+ })
+ @interface EntityType {}
+
+ /**
+ * No-op TextClassifier.
+ * This may be used to turn off TextClassifier features.
+ */
+ TextClassifier NO_OP = new TextClassifier() {
+
+ @Override
+ public TextSelection suggestSelection(
+ CharSequence text, int selectionStartIndex, int selectionEndIndex) {
+ return new TextSelection.Builder(selectionStartIndex, selectionEndIndex).build();
+ }
+
+ @Override
+ public TextClassificationResult getTextClassificationResult(
+ CharSequence text, int startIndex, int endIndex) {
+ return TextClassificationResult.EMPTY;
+ }
+
+ @Override
+ public LinksInfo getLinks(CharSequence text, int linkMask) {
+ return LinksInfo.NO_OP;
+ }
+ };
+
+ /**
+ * Returns suggested text selection indices, recognized types and their associated confidence
+ * scores. The selections are ordered from highest to lowest scoring.
+ *
+ * @param text text providing context for the selected text (which is specified
+ * by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex)
+ * @param selectionStartIndex start index of the selected part of text
+ * @param selectionEndIndex end index of the selected part of text
+ *
+ * @throws IllegalArgumentException if text is null; selectionStartIndex is negative;
+ * selectionEndIndex is greater than text.length() or less than selectionStartIndex
+ */
+ @NonNull
+ TextSelection suggestSelection(
+ @NonNull CharSequence text,
+ @IntRange(from = 0) int selectionStartIndex,
+ @IntRange(from = 0) int selectionEndIndex);
+
+ /**
+ * Returns a {@link TextClassificationResult} object that can be used to generate a widget for
+ * handling the classified text.
+ *
+ * @param text text providing context for the text to classify (which is specified
+ * by the sub sequence starting at startIndex and ending at endIndex)
+ * @param startIndex start index of the text to classify
+ * @param endIndex end index of the text to classify
+ *
+ * @throws IllegalArgumentException if text is null; startIndex is negative;
+ * endIndex is greater than text.length() or less than startIndex
+ */
+ @NonNull
+ TextClassificationResult getTextClassificationResult(
+ @NonNull CharSequence text, int startIndex, int endIndex);
+
+ /**
+ * Returns a {@link LinksInfo} that may be applied to the text to annotate it with links
+ * information.
+ *
+ * @param text the text to generate annotations for
+ * @param linkMask See {@link android.text.util.Linkify} for a list of linkMasks that may be
+ * specified. Subclasses of this interface may specify additional linkMasks
+ *
+ * @throws IllegalArgumentException if text is null
+ */
+ LinksInfo getLinks(@NonNull CharSequence text, int linkMask);
+}
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
new file mode 100644
index 0000000..72796cf
--- /dev/null
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.text.SmartSelection;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+
+import java.io.FileNotFoundException;
+
+/**
+ * Default implementation of the {@link TextClassifier} interface.
+ *
+ * <p>This class uses machine learning to recognize entities in text.
+ * Unless otherwise stated, methods of this class are blocking operations and should most
+ * likely not be called on the UI thread.
+ *
+ * @hide
+ */
+final class TextClassifierImpl implements TextClassifier {
+
+ private static final String LOG_TAG = "TextClassifierImpl";
+
+ private final Context mContext;
+ private final ParcelFileDescriptor mFd;
+ private SmartSelection mSmartSelection;
+
+ TextClassifierImpl(Context context, ParcelFileDescriptor fd) {
+ mContext = Preconditions.checkNotNull(context);
+ mFd = Preconditions.checkNotNull(fd);
+ }
+
+ @Override
+ public TextSelection suggestSelection(
+ @NonNull CharSequence text, int selectionStartIndex, int selectionEndIndex) {
+ validateInput(text, selectionStartIndex, selectionEndIndex);
+ try {
+ if (text.length() > 0) {
+ final String string = text.toString();
+ final int[] startEnd = getSmartSelection()
+ .suggest(string, selectionStartIndex, selectionEndIndex);
+ final int start = startEnd[0];
+ final int end = startEnd[1];
+ if (start >= 0 && end <= string.length() && start <= end) {
+ final String type = getSmartSelection().classifyText(string, start, end);
+ return new TextSelection.Builder(start, end)
+ .setEntityType(type, 1.0f)
+ .build();
+ } else {
+ // We can not trust the result. Log the issue and ignore the result.
+ Log.d(LOG_TAG, "Got bad indices for input text. Ignoring result.");
+ }
+ }
+ } catch (Throwable t) {
+ // Avoid throwing from this method. Log the error.
+ Log.e(LOG_TAG,
+ "Error suggesting selection for text. No changes to selection suggested.",
+ t);
+ }
+ // Getting here means something went wrong, return a NO_OP result.
+ return TextClassifier.NO_OP.suggestSelection(
+ text, selectionStartIndex, selectionEndIndex);
+ }
+
+ @Override
+ public TextClassificationResult getTextClassificationResult(
+ @NonNull CharSequence text, int startIndex, int endIndex) {
+ validateInput(text, startIndex, endIndex);
+ try {
+ if (text.length() > 0) {
+ final CharSequence classified = text.subSequence(startIndex, endIndex);
+ String type = getSmartSelection()
+ .classifyText(text.toString(), startIndex, endIndex);
+ if (!TextUtils.isEmpty(type)) {
+ type = type.toLowerCase().trim();
+ // TODO: Added this log for debug only. Remove before release.
+ Log.d(LOG_TAG, String.format("Classification type: %s", type));
+ final Intent intent;
+ final String title;
+ switch (type) {
+ case TextClassifier.TYPE_EMAIL:
+ intent = new Intent(Intent.ACTION_SENDTO);
+ intent.setData(Uri.parse(String.format("mailto:%s", text)));
+ title = mContext.getString(com.android.internal.R.string.email);
+ return createClassificationResult(classified, type, intent, title);
+ case TextClassifier.TYPE_PHONE:
+ intent = new Intent(Intent.ACTION_DIAL);
+ intent.setData(Uri.parse(String.format("tel:%s", text)));
+ title = mContext.getString(com.android.internal.R.string.dial);
+ return createClassificationResult(classified, type, intent, title);
+ case TextClassifier.TYPE_ADDRESS:
+ intent = new Intent(Intent.ACTION_VIEW);
+ intent.setData(Uri.parse(String.format("geo:0,0?q=%s", text)));
+ title = mContext.getString(com.android.internal.R.string.map);
+ return createClassificationResult(classified, type, intent, title);
+ default:
+ // No classification type found. Return a no-op result.
+ break;
+ // TODO: Add other classification types.
+ }
+ }
+ }
+ } catch (Throwable t) {
+ // Avoid throwing from this method. Log the error.
+ Log.e(LOG_TAG, "Error getting assist info.", t);
+ }
+ // Getting here means something went wrong, return a NO_OP result.
+ return TextClassifier.NO_OP.getTextClassificationResult(text, startIndex, endIndex);
+ }
+
+ @Override
+ public LinksInfo getLinks(@NonNull CharSequence text, int linkMask) {
+ // TODO: Implement
+ return TextClassifier.NO_OP.getLinks(text, linkMask);
+ }
+
+ private synchronized SmartSelection getSmartSelection() throws FileNotFoundException {
+ if (mSmartSelection == null) {
+ mSmartSelection = new SmartSelection(mFd.getFd());
+ }
+ return mSmartSelection;
+ }
+
+ private TextClassificationResult createClassificationResult(
+ CharSequence text, String type, Intent intent, String label) {
+ TextClassificationResult.Builder builder = new TextClassificationResult.Builder()
+ .setText(text.toString())
+ .setEntityType(type, 1.0f /* confidence */)
+ .setIntent(intent)
+ .setOnClickListener(TextClassificationResult.createStartActivityOnClick(
+ mContext, intent))
+ .setLabel(label);
+ PackageManager pm = mContext.getPackageManager();
+ ResolveInfo resolveInfo = pm.resolveActivity(intent, 0);
+ // TODO: If the resolveInfo is the "chooser", do not set the package name and use a
+ // default icon for this classification type.
+ intent.setPackage(resolveInfo.activityInfo.packageName);
+ Drawable icon = resolveInfo.activityInfo.loadIcon(pm);
+ if (icon == null) {
+ icon = resolveInfo.loadIcon(pm);
+ }
+ builder.setIcon(icon);
+ return builder.build();
+ }
+
+ /**
+ * @throws IllegalArgumentException if text is null; startIndex is negative;
+ * endIndex is greater than text.length() or less than startIndex
+ */
+ private static void validateInput(@NonNull CharSequence text, int startIndex, int endIndex) {
+ Preconditions.checkArgument(text != null);
+ Preconditions.checkArgument(startIndex >= 0);
+ Preconditions.checkArgument(endIndex <= text.length());
+ Preconditions.checkArgument(endIndex >= startIndex);
+ }
+}
diff --git a/core/java/android/view/textclassifier/TextLanguage.java b/core/java/android/view/textclassifier/TextLanguage.java
new file mode 100644
index 0000000..d94d163
--- /dev/null
+++ b/core/java/android/view/textclassifier/TextLanguage.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier;
+
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Specifies detected languages for a section of text indicated by a start and end index.
+ */
+public final class TextLanguage {
+
+ private final int mStartIndex;
+ private final int mEndIndex;
+ @NonNull private final EntityConfidence<Locale> mLanguageConfidence;
+ @NonNull private final List<Locale> mLanguages;
+
+ private TextLanguage(
+ int startIndex, int endIndex, @NonNull EntityConfidence<Locale> languageConfidence) {
+ mStartIndex = startIndex;
+ mEndIndex = endIndex;
+ mLanguageConfidence = new EntityConfidence<>(languageConfidence);
+ mLanguages = mLanguageConfidence.getEntities();
+ }
+
+ /**
+ * Returns the start index of the detected languages in the text provided to generate this
+ * object.
+ */
+ public int getStartIndex() {
+ return mStartIndex;
+ }
+
+ /**
+ * Returns the end index of the detected languages in the text provided to generate this object.
+ */
+ public int getEndIndex() {
+ return mEndIndex;
+ }
+
+ /**
+ * Returns the number of languages found in the classified text.
+ */
+ @IntRange(from = 0)
+ public int getLanguageCount() {
+ return mLanguages.size();
+ }
+
+ /**
+ * Returns the language locale at the specified index.
+ * Language locales are ordered from high confidence to low confidence.
+ *
+ * @throws IndexOutOfBoundsException if the specified index is out of range.
+ * @see #getLanguageCount() for the number of language locales available.
+ */
+ @NonNull
+ public Locale getLanguage(int index) {
+ return mLanguages.get(index);
+ }
+
+ /**
+ * Returns the confidence score for the specified language. The value ranges from
+ * 0 (low confidence) to 1 (high confidence). 0 indicates that the language was
+ * not found for the classified text.
+ */
+ @FloatRange(from = 0.0, to = 1.0)
+ public float getConfidenceScore(@Nullable Locale language) {
+ return mLanguageConfidence.getConfidenceScore(language);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("TextLanguage {%d, %d, %s}",
+ mStartIndex, mEndIndex, mLanguageConfidence);
+ }
+
+ /**
+ * Builder to build {@link TextLanguage} objects.
+ */
+ public static final class Builder {
+
+ private final int mStartIndex;
+ private final int mEndIndex;
+ @NonNull private final EntityConfidence<Locale> mLanguageConfidence =
+ new EntityConfidence<>();
+
+ /**
+ * Creates a builder to build {@link TextLanguage} objects.
+ *
+ * @param startIndex the start index of the detected languages in the text provided
+ * to generate the result
+ * @param endIndex the end index of the detected languages in the text provided
+ * to generate the result. Must be greater than startIndex
+ */
+ public Builder(@IntRange(from = 0) int startIndex, @IntRange(from = 0) int endIndex) {
+ Preconditions.checkArgument(startIndex >= 0);
+ Preconditions.checkArgument(endIndex > startIndex);
+ mStartIndex = startIndex;
+ mEndIndex = endIndex;
+ }
+
+ /**
+ * Sets a language locale with the associated confidence score.
+ */
+ public Builder setLanguage(
+ @NonNull Locale locale, @FloatRange(from = 0.0, to = 1.0) float confidenceScore) {
+ mLanguageConfidence.setEntityType(locale, confidenceScore);
+ return this;
+ }
+
+ /**
+ * Builds and returns a {@link TextLanguage}.
+ */
+ public TextLanguage build() {
+ return new TextLanguage(mStartIndex, mEndIndex, mLanguageConfidence);
+ }
+ }
+}
diff --git a/core/java/android/view/textclassifier/TextSelection.java b/core/java/android/view/textclassifier/TextSelection.java
new file mode 100644
index 0000000..3172c13
--- /dev/null
+++ b/core/java/android/view/textclassifier/TextSelection.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier;
+
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.view.textclassifier.TextClassifier.EntityType;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+
+/**
+ * Information about where text selection should be.
+ */
+public final class TextSelection {
+
+ private final int mStartIndex;
+ private final int mEndIndex;
+ @NonNull private final EntityConfidence<String> mEntityConfidence;
+ @NonNull private final List<String> mEntities;
+
+ private TextSelection(
+ int startIndex, int endIndex, @NonNull EntityConfidence<String> entityConfidence) {
+ mStartIndex = startIndex;
+ mEndIndex = endIndex;
+ mEntityConfidence = new EntityConfidence<>(entityConfidence);
+ mEntities = mEntityConfidence.getEntities();
+ }
+
+ /**
+ * Returns the start index of the text selection.
+ */
+ public int getSelectionStartIndex() {
+ return mStartIndex;
+ }
+
+ /**
+ * Returns the end index of the text selection.
+ */
+ public int getSelectionEndIndex() {
+ return mEndIndex;
+ }
+
+ /**
+ * Returns the number of entities found in the classified text.
+ */
+ @IntRange(from = 0)
+ public int getEntityCount() {
+ return mEntities.size();
+ }
+
+ /**
+ * Returns the entity at the specified index. Entities are ordered from high confidence
+ * to low confidence.
+ *
+ * @throws IndexOutOfBoundsException if the specified index is out of range.
+ * @see #getEntityCount() for the number of entities available.
+ */
+ @NonNull
+ public @EntityType String getEntity(int index) {
+ return mEntities.get(index);
+ }
+
+ /**
+ * Returns the confidence score for the specified entity. The value ranges from
+ * 0 (low confidence) to 1 (high confidence). 0 indicates that the entity was not found for the
+ * classified text.
+ */
+ @FloatRange(from = 0.0, to = 1.0)
+ public float getConfidenceScore(@EntityType String entity) {
+ return mEntityConfidence.getConfidenceScore(entity);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("TextSelection {%d, %d, %s}",
+ mStartIndex, mEndIndex, mEntityConfidence);
+ }
+
+ /**
+ * Builder used to build {@link TextSelection} objects.
+ */
+ public static final class Builder {
+
+ private final int mStartIndex;
+ private final int mEndIndex;
+ @NonNull private final EntityConfidence<String> mEntityConfidence =
+ new EntityConfidence<>();
+
+ /**
+ * Creates a builder used to build {@link TextSelection} objects.
+ *
+ * @param startIndex the start index of the text selection.
+ * @param endIndex the end index of the text selection. Must be greater than startIndex
+ */
+ public Builder(@IntRange(from = 0) int startIndex, @IntRange(from = 0) int endIndex) {
+ Preconditions.checkArgument(startIndex >= 0);
+ Preconditions.checkArgument(endIndex > startIndex);
+ mStartIndex = startIndex;
+ mEndIndex = endIndex;
+ }
+
+ /**
+ * Sets an entity type for the classified text and assigns a confidence score.
+ *
+ * @param confidenceScore a value from 0 (low confidence) to 1 (high confidence).
+ * 0 implies the entity does not exist for the classified text.
+ * Values greater than 1 are clamped to 1.
+ */
+ public Builder setEntityType(
+ @NonNull @EntityType String type,
+ @FloatRange(from = 0.0, to = 1.0) float confidenceScore) {
+ mEntityConfidence.setEntityType(type, confidenceScore);
+ return this;
+ }
+
+ /**
+ * Builds and returns {@link TextSelection} object.
+ */
+ public TextSelection build() {
+ return new TextSelection(mStartIndex, mEndIndex, mEntityConfidence);
+ }
+ }
+}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 2fc8ec9..f7f9a81 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -60,8 +60,6 @@
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.StaticLayout;
-import android.text.TextClassification;
-import android.text.TextSelection;
import android.text.TextUtils;
import android.text.method.KeyListener;
import android.text.method.MetaKeyKeyListener;
@@ -108,6 +106,8 @@
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
+import android.view.textclassifier.TextClassificationResult;
+import android.view.textclassifier.TextSelection;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.TextView.Drawables;
import android.widget.TextView.OnEditorActionListener;
@@ -149,7 +149,7 @@
private static final String UNDO_OWNER_TAG = "Editor";
// Ordering constants used to place the Action Mode or context menu items in their menu.
- // Reserve 1 for the app that the ASSIST logic suggests as the best app to handle the selection.
+ private static final int MENU_ITEM_ORDER_ASSIST = 1;
private static final int MENU_ITEM_ORDER_UNDO = 2;
private static final int MENU_ITEM_ORDER_REDO = 3;
private static final int MENU_ITEM_ORDER_SHARE = 4;
@@ -238,6 +238,8 @@
private boolean mPreserveSelection;
private boolean mRestartActionModeOnNextRefresh;
+ private TextClassificationResult mTextClassificationResult;
+
boolean mIsBeingLongClicked;
private SuggestionsPopupWindow mSuggestionsPopupWindow;
@@ -1889,7 +1891,7 @@
mInsertionPointCursorController.invalidateHandle();
}
if (mTextActionMode != null) {
- mTextActionMode.invalidate();
+ invalidateActionMode(getTextClassifierInfo(false));
}
}
@@ -1982,12 +1984,12 @@
if (mRestartActionModeOnNextRefresh) {
// To avoid distraction, newly start action mode only when selection action
// mode is being restarted.
- startSelectionActionMode();
+ startSelectionActionMode(getTextClassifierInfo(true));
}
} else if (selectionController == null || !selectionController.isActive()) {
// Insertion action mode is active. Avoid dismissing the selection.
stopTextActionModeWithPreservingSelection();
- startSelectionActionMode();
+ startSelectionActionMode(getTextClassifierInfo(true));
} else {
mTextActionMode.invalidateContentRect();
}
@@ -2031,7 +2033,8 @@
*
* @return true if the selection mode was actually started.
*/
- boolean startSelectionActionMode() {
+ boolean startSelectionActionMode(@Nullable TextClassificationResult textClassificationResult) {
+ mTextClassificationResult = textClassificationResult;
boolean selectionStarted = startSelectionActionModeInternal();
if (selectionStarted) {
getSelectionController().show();
@@ -2040,6 +2043,40 @@
return selectionStarted;
}
+ private boolean startSelectionActionModeWithTextAssistant() {
+ return startSelectionActionMode(getTextClassifierInfo(true));
+ }
+
+ private void invalidateActionMode(TextClassificationResult textClassificationResult) {
+ mTextClassificationResult = textClassificationResult;
+ mTextActionMode.invalidate();
+ }
+
+ // TODO: Make this a non-blocking call.
+ private TextClassificationResult getTextClassifierInfo(boolean updateSelection) {
+ // TODO: Trim the text so that only text necessary to provide context of the selected
+ // text is sent to the assistant.
+ final int trimStartIndex = 0;
+ final int trimEndIndex = mTextView.getText().length();
+ CharSequence trimmedText =
+ mTextView.getText().subSequence(trimStartIndex, trimEndIndex);
+ int startIndex = mTextView.getSelectionStart() - trimStartIndex;
+ int endIndex = mTextView.getSelectionEnd() - trimStartIndex;
+
+ if (updateSelection) {
+ TextSelection textSelection = mTextView.getTextClassifier()
+ .suggestSelection(trimmedText, startIndex, endIndex);
+ startIndex = Math.max(0, textSelection.getSelectionStartIndex() + trimStartIndex);
+ endIndex = Math.min(mTextView.getText().length(),
+ textSelection.getSelectionEndIndex() + trimStartIndex);
+ Selection.setSelection((Spannable) mTextView.getText(), startIndex, endIndex);
+ return getTextClassifierInfo(false);
+ }
+
+ return mTextView.getTextClassifier()
+ .getTextClassificationResult(trimmedText, startIndex, endIndex);
+ }
+
/**
* If the TextView allows text selection, selects the current word when no existing selection
* was available and starts a drag.
@@ -2086,7 +2123,7 @@
}
if (mTextActionMode != null) {
// Text action mode is already started
- mTextActionMode.invalidate();
+ invalidateActionMode(getTextClassifierInfo(false));
return false;
}
@@ -3744,8 +3781,7 @@
private final Path mSelectionPath = new Path();
private final RectF mSelectionBounds = new RectF();
private final boolean mHasSelection;
-
- private int mHandleHeight;
+ private final int mHandleHeight;
public TextActionModeCallback(boolean hasSelection) {
mHasSelection = hasSelection;
@@ -3765,18 +3801,19 @@
if (insertionController != null) {
insertionController.getHandle();
mHandleHeight = mSelectHandleCenter.getMinimumHeight();
+ } else {
+ mHandleHeight = 0;
}
}
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
- TextClassification textClassification = updateSelectionWithTextAssistant();
-
mode.setTitle(null);
mode.setSubtitle(null);
mode.setTitleOptionalHint(true);
- populateMenuWithItems(menu, textClassification);
+ populateMenuWithItems(menu);
+ updateAssistMenuItem(menu, mTextClassificationResult);
Callback customCallback = getCustomCallback();
if (customCallback != null) {
@@ -3802,30 +3839,13 @@
}
}
- private TextClassification updateSelectionWithTextAssistant() {
- // Trim the text so that only text necessary to provide context of the selected text is
- // sent to the assistant.
- CharSequence trimmedText = mTextView.getText();
- int textLength = mTextView.getText().length();
- int trimStartIndex = 0;
- int startIndex = mTextView.getSelectionStart() - trimStartIndex;
- int endIndex = mTextView.getSelectionEnd() - trimStartIndex;
- TextSelection textSelection = mTextView.getTextAssistant()
- .suggestSelection(trimmedText, startIndex, endIndex);
- Selection.setSelection(
- (Spannable) mTextView.getText(),
- Math.max(0, textSelection.getSelectionStartIndex() + trimStartIndex),
- Math.min(textLength, textSelection.getSelectionEndIndex() + trimStartIndex));
- return textSelection.getTextClassification();
- }
-
private Callback getCustomCallback() {
return mHasSelection
? mCustomSelectionActionModeCallback
: mCustomInsertionActionModeCallback;
}
- private void populateMenuWithItems(Menu menu, TextClassification textClassification) {
+ private void populateMenuWithItems(Menu menu) {
if (mTextView.canCut()) {
menu.add(Menu.NONE, TextView.ID_CUT, MENU_ITEM_ORDER_CUT,
com.android.internal.R.string.cut)
@@ -3855,13 +3875,13 @@
updateSelectAllItem(menu);
updateReplaceItem(menu);
- updateAssistMenuItem(menu, textClassification);
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
updateSelectAllItem(menu);
updateReplaceItem(menu);
+ updateAssistMenuItem(menu, mTextClassificationResult);
Callback customCallback = getCustomCallback();
if (customCallback != null) {
@@ -3894,10 +3914,16 @@
}
}
- private void updateAssistMenuItem(Menu menu, TextClassification textClassification) {
- // TODO: Find the best app available to handle the selected text based on information in
- // the TextClassification object.
- // Add app icon + intent to trigger app to the menu.
+ private void updateAssistMenuItem(
+ Menu menu, TextClassificationResult textClassificationResult) {
+ menu.removeItem(TextView.ID_ASSIST);
+ if (textClassificationResult != null
+ && textClassificationResult.getIcon() != null
+ && textClassificationResult.getOnClickListener() != null) {
+ menu.add(Menu.NONE, TextView.ID_ASSIST, MENU_ITEM_ORDER_ASSIST, null)
+ .setIcon(textClassificationResult.getIcon())
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
+ }
}
@Override
@@ -3909,6 +3935,10 @@
if (customCallback != null && customCallback.onActionItemClicked(mode, item)) {
return true;
}
+ if (TextView.ID_ASSIST == item.getItemId() && mTextClassificationResult != null) {
+ mTextClassificationResult.getOnClickListener().onClick(mTextView);
+ stopTextActionMode();
+ }
return mTextView.onTextContextMenuItem(item.getItemId());
}
@@ -3916,6 +3946,7 @@
public void onDestroyActionMode(ActionMode mode) {
// Clear mTextActionMode not to recursively destroy action mode by clearing selection.
mTextActionMode = null;
+ mTextClassificationResult = null;
Callback customCallback = getCustomCallback();
if (customCallback != null) {
customCallback.onDestroyActionMode(mode);
@@ -4733,7 +4764,7 @@
}
positionAtCursorOffset(offset, false);
if (mTextActionMode != null) {
- mTextActionMode.invalidate();
+ invalidateActionMode(getTextClassifierInfo(false));
}
}
@@ -4817,7 +4848,7 @@
}
updateDrawable();
if (mTextActionMode != null) {
- mTextActionMode.invalidate();
+ invalidateActionMode(getTextClassifierInfo(false));
}
}
@@ -5465,7 +5496,8 @@
resetDragAcceleratorState();
if (mTextView.hasSelection()) {
- startSelectionActionMode();
+ // TODO: Do not invoke the text assistant if this was a drag selection.
+ startSelectionActionMode(getTextClassifierInfo(true));
}
break;
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index b1fc67e..2f303cd 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -77,8 +77,6 @@
import android.text.Spanned;
import android.text.SpannedString;
import android.text.StaticLayout;
-import android.text.TextAssistant;
-import android.text.TextClassificationManager;
import android.text.TextDirectionHeuristic;
import android.text.TextDirectionHeuristics;
import android.text.TextPaint;
@@ -146,6 +144,8 @@
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
+import android.view.textclassifier.TextClassificationManager;
+import android.view.textclassifier.TextClassifier;
import android.view.textservice.SpellCheckerSubtype;
import android.view.textservice.TextServicesManager;
import android.widget.RemoteViews.RemoteView;
@@ -352,6 +352,8 @@
private boolean mPreDrawRegistered;
private boolean mPreDrawListenerDetached;
+ private TextClassifier mTextClassifier;
+
// A flag to prevent repeated movements from escaping the enclosing text view. The idea here is
// that if a user is holding down a movement key to traverse text, we shouldn't also traverse
// the view hierarchy. On the other hand, if the user is using the movement key to traverse
@@ -9890,7 +9892,7 @@
Selection.setSelection((Spannable) text, start, end);
// Make sure selection mode is engaged.
if (mEditor != null) {
- mEditor.startSelectionActionMode();
+ mEditor.startSelectionActionMode(null);
}
return true;
}
@@ -10034,6 +10036,7 @@
static final int ID_SHARE = android.R.id.shareText;
static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText;
static final int ID_REPLACE = android.R.id.replaceText;
+ static final int ID_ASSIST = android.R.id.textAssist;
/**
* Called when a context menu option for the text view is selected. Currently
@@ -10258,33 +10261,30 @@
return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback;
}
- private TextAssistant mTextAssistant;
-
/**
- * Sets the {@link TextAssistant} for this TextView.
- * If null, this TextView uses the default TextAssistant which comes from the Activity.
+ * Sets the {@link TextClassifier} for this TextView.
*/
- public void setTextAssistant(TextAssistant textAssistant) {
- mTextAssistant = textAssistant;
+ public void setTextClassifier(@Nullable TextClassifier textClassifier) {
+ mTextClassifier = textClassifier;
}
/**
- * Returns the {@link TextAssistant} used by this TextView.
- * If no TextAssistant is set, it'll use the one from this TextView's {@link Activity} or
- * {@link Context}. If no TextAssistant is found, it'll use a no-op TextAssistant.
+ * Returns the {@link TextClassifier} used by this TextView.
+ * If no TextClassifier has been set, this TextView uses the default set by the
+ * {@link TextClassificationManager}.
*/
- public TextAssistant getTextAssistant() {
- if (mTextAssistant != null) {
- return mTextAssistant;
+ @NonNull
+ public TextClassifier getTextClassifier() {
+ if (mTextClassifier == null) {
+ TextClassificationManager tcm =
+ mContext.getSystemService(TextClassificationManager.class);
+ if (tcm != null) {
+ mTextClassifier = tcm.getDefaultTextClassifier();
+ } else {
+ mTextClassifier = TextClassifier.NO_OP;
+ }
}
- if (mContext instanceof Activity) {
- mTextAssistant = ((Activity) mContext).getTextAssistant();
- } else {
- // The context of this TextView should be an Activity. If it is not and no
- // text assistant has been set, return the TextClassificationManager.
- mTextAssistant = mContext.getSystemService(TextClassificationManager.class);
- }
- return mTextAssistant;
+ return mTextClassifier;
}
/**
diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java
index 8d11783..a94b161 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -95,39 +95,32 @@
}
}
+ private static int compareNullableCharSequences(@Nullable CharSequence c1,
+ @Nullable CharSequence c2) {
+ // For historical reasons, an empty text needs to put at the last.
+ final boolean empty1 = TextUtils.isEmpty(c1);
+ final boolean empty2 = TextUtils.isEmpty(c2);
+ if (empty1 || empty2) {
+ return (empty1 ? 1 : 0) - (empty2 ? 1 : 0);
+ }
+ return c1.toString().compareTo(c2.toString());
+ }
+
@Override
public int compareTo(ImeSubtypeListItem other) {
- if (TextUtils.isEmpty(mImeName)) {
- return 1;
+ int result = compareNullableCharSequences(mImeName, other.mImeName);
+ if (result != 0) {
+ return result;
}
- if (TextUtils.isEmpty(other.mImeName)) {
- return -1;
+ result = compareNullableCharSequences(mSubtypeName, other.mSubtypeName);
+ if (result != 0) {
+ return result;
}
- if (!TextUtils.equals(mImeName, other.mImeName)) {
- return mImeName.toString().compareTo(other.mImeName.toString());
+ result = (mIsSystemLocale ? -1 : 0) - (other.mIsSystemLocale ? -1 : 0);
+ if (result != 0) {
+ return result;
}
- if (TextUtils.equals(mSubtypeName, other.mSubtypeName)) {
- return 0;
- }
- if (mIsSystemLocale) {
- return -1;
- }
- if (other.mIsSystemLocale) {
- return 1;
- }
- if (mIsSystemLanguage) {
- return -1;
- }
- if (other.mIsSystemLanguage) {
- return 1;
- }
- if (TextUtils.isEmpty(mSubtypeName)) {
- return 1;
- }
- if (TextUtils.isEmpty(other.mSubtypeName)) {
- return -1;
- }
- return mSubtypeName.toString().compareTo(other.mSubtypeName.toString());
+ return (mIsSystemLanguage ? -1 : 0) - (other.mIsSystemLanguage ? -1 : 0);
}
@Override
diff --git a/core/java/com/android/internal/logging/LogBuilder.java b/core/java/com/android/internal/logging/LogBuilder.java
index 7eda3da..2d78979 100644
--- a/core/java/com/android/internal/logging/LogBuilder.java
+++ b/core/java/com/android/internal/logging/LogBuilder.java
@@ -23,6 +23,7 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
/**
* Helper class to assemble more complex logs.
*
@@ -31,6 +32,13 @@
public class LogBuilder {
private static final String TAG = "LogBuilder";
+
+ // Min required eventlog line length.
+ // See: android/util/cts/EventLogTest.java
+ // Size checks enforced here are intended only as sanity checks;
+ // your logs may be truncated earlier. Please log responsibly.
+ public static final int MAX_SERIALIZED_SIZE = 4000;
+
private SparseArray<Object> entries = new SparseArray();
public LogBuilder(int mainCategory) {
@@ -97,7 +105,11 @@
throw new IllegalArgumentException(
"Value must be loggable type - int, long, float, String");
}
- entries.put(tag, value);
+ if (value.toString().getBytes().length > MAX_SERIALIZED_SIZE) {
+ Log.i(TAG, "Log value too long, omitted: " + value.toString());
+ } else {
+ entries.put(tag, value);
+ }
return this;
}
@@ -198,18 +210,23 @@
out[i * 2] = entries.keyAt(i);
out[i * 2 + 1] = entries.valueAt(i);
}
+ int size = out.toString().getBytes().length;
+ if (size > MAX_SERIALIZED_SIZE) {
+ Log.i(TAG, "Log line too long, did not emit: " + size + " bytes.");
+ throw new RuntimeException();
+ }
return out;
}
public void deserialize(Object[] items) {
int i = 0;
- while(i < items.length) {
+ while (i < items.length) {
Object key = items[i++];
Object value = i < items.length ? items[i++] : null;
if (key instanceof Integer) {
entries.put((Integer) key, value);
} else {
- Log.i(TAG, "Invalid key " + key.toString());
+ Log.i(TAG, "Invalid key " + key.toString());
}
}
}
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
index 16c2719..b90336c 100644
--- a/core/java/com/android/internal/logging/MetricsLogger.java
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -94,9 +94,6 @@
}
public static void action(LogBuilder content) {
- //EventLog.writeEvent(524292, content.serialize());
- // Below would be the *right* way to do this, using the generated
- // EventLogTags method, but that doesn't work.
if (content.getType() == MetricsEvent.TYPE_UNKNOWN) {
content.setType(MetricsEvent.TYPE_ACTION);
}
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 5235116..dfa672d 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1240,6 +1240,10 @@
<!-- An XML resource with the application's Network Security Config. -->
<attr name="networkSecurityConfig" format="reference" />
+ <!-- When an application is partitioned into splits, this is the name of the
+ split that contains the defined component. -->
+ <attr name="splitName" format="string" />
+
<!-- The <code>manifest</code> tag is the root of an
<code>AndroidManifest.xml</code> file,
describing the contents of an Android package (.apk) file. One
@@ -1823,6 +1827,8 @@
<attr name="singleUser" />
<attr name="directBootAware" />
<attr name="visibleToInstantApps" />
+ <!-- The code for this component is located in the given split. -->
+ <attr name="splitName" />
</declare-styleable>
<!-- Attributes that can be supplied in an AndroidManifest.xml
@@ -1913,6 +1919,8 @@
must also be {@link android.R.attr#exported} if this flag is set. -->
<attr name="externalService" format="boolean" />
<attr name="visibleToInstantApps" />
+ <!-- The code for this component is located in the given split. -->
+ <attr name="splitName" />
</declare-styleable>
<!-- The <code>receiver</code> tag declares an
@@ -2036,6 +2044,8 @@
<attr name="onTopLauncher" format="boolean" />
<attr name="rotationAnimation" />
<attr name="visibleToInstantApps" />
+ <!-- The code for this component is located in the given split. -->
+ <attr name="splitName" />
</declare-styleable>
<!-- The <code>activity-alias</code> tag declares a new
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 7de48d3..c36279c 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2090,6 +2090,7 @@
<item>com.android.server.notification.ImportanceExtractor</item>
<item>com.android.server.notification.NotificationIntrusivenessExtractor</item>
<item>com.android.server.notification.VisibilityExtractor</item>
+ <item>com.android.server.notification.BadgeExtractor</item>
</string-array>
<!-- Flag indicating that this device does not rotate and will always remain in its default
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index f351b70..613616f 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -97,6 +97,7 @@
<item type="id" name="redo" />
<item type="id" name="replaceText" />
<item type="id" name="shareText" />
+ <item type="id" name="textAssist" />
<item type="id" name="selection_start_handle" />
<item type="id" name="selection_end_handle" />
<item type="id" name="insertion_handle" />
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 40d0e45..30da26b 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2793,6 +2793,7 @@
</public-group>
<public-group type="id" first-id="0x01020041">
+ <public name="textAssist" />
</public-group>
<public type="attr" name="primaryContentAlpha" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d09b190..eece9fc 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2589,6 +2589,15 @@
<!-- Title for EditText context menu [CHAR LIMIT=20] -->
<string name="editTextMenuTitle">Text actions</string>
+ <!-- Label for item in the text selection menu to trigger an Email app [CHAR LIMIT=20] -->
+ <string name="email">Email</string>
+
+ <!-- Label for item in the text selection menu to trigger a Dialer app [CHAR LIMIT=20] -->
+ <string name="dial">Dial</string>
+
+ <!-- Label for item in the text selection menu to trigger a Map app [CHAR LIMIT=20] -->
+ <string name="map">Map</string>
+
<!-- If the device is getting low on internal storage, a notification is shown to the user. This is the title of that notification. -->
<string name="low_internal_storage_view_title">Storage space running out</string>
<!-- If the device is getting low on internal storage, a notification is shown to the user. This is the message of that notification. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6eb3bee..16356c7 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -476,6 +476,9 @@
<java-symbol type="string" name="replace" />
<java-symbol type="string" name="undo" />
<java-symbol type="string" name="redo" />
+ <java-symbol type="string" name="email" />
+ <java-symbol type="string" name="dial" />
+ <java-symbol type="string" name="map" />
<java-symbol type="string" name="textSelectionCABTitle" />
<java-symbol type="string" name="BaMmi" />
<java-symbol type="string" name="CLIRDefaultOffNextCallOff" />
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
index 34c34d7..dc75417 100644
--- a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
@@ -33,7 +33,8 @@
import java.util.List;
public class InputMethodSubtypeSwitchingControllerTest extends InstrumentationTestCase {
- private static final String DUMMY_PACKAGE_NAME = "dymmy package name";
+ private static final String DUMMY_PACKAGE_NAME = "dummy package name";
+ private static final String DUMMY_IME_LABEL = "dummy ime label";
private static final String DUMMY_SETTING_ACTIVITY_NAME = "";
private static final boolean DUMMY_IS_AUX_IME = false;
private static final boolean DUMMY_FORCE_DEFAULT = false;
@@ -88,6 +89,35 @@
}
}
+ private static ImeSubtypeListItem createDummyItem(String imeName,
+ String subtypeName, String subtypeLocale, int subtypeIndex, String systemLocale) {
+ final ResolveInfo ri = new ResolveInfo();
+ final ServiceInfo si = new ServiceInfo();
+ final ApplicationInfo ai = new ApplicationInfo();
+ ai.packageName = DUMMY_PACKAGE_NAME;
+ ai.enabled = true;
+ si.applicationInfo = ai;
+ si.enabled = true;
+ si.packageName = DUMMY_PACKAGE_NAME;
+ si.name = imeName;
+ si.exported = true;
+ si.nonLocalizedLabel = DUMMY_IME_LABEL;
+ ri.serviceInfo = si;
+ ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+ subtypes.add(new InputMethodSubtypeBuilder()
+ .setSubtypeNameResId(0)
+ .setSubtypeIconResId(0)
+ .setSubtypeLocale(subtypeLocale)
+ .setIsAsciiCapable(true)
+ .build());
+ final InputMethodInfo imi = new InputMethodInfo(ri, DUMMY_IS_AUX_IME,
+ DUMMY_SETTING_ACTIVITY_NAME, subtypes, DUMMY_IS_DEFAULT_RES_ID,
+ DUMMY_FORCE_DEFAULT, true /* supportsSwitchingToNextInputMethod */,
+ false /* supportsDismissingWindow */);
+ return new ImeSubtypeListItem(imeName, subtypeName, imi, subtypeIndex, subtypeLocale,
+ systemLocale);
+ }
+
private static List<ImeSubtypeListItem> createEnabledImeSubtypes() {
final List<ImeSubtypeListItem> items = new ArrayList<>();
addDummyImeSubtypeListItems(items, "LatinIme", "LatinIme", Arrays.asList("en_US", "fr"),
@@ -329,4 +359,56 @@
assertFalse(item_e.mIsSystemLocale);
assertFalse(item_EN_US.mIsSystemLocale);
}
+
+ @SmallTest
+ public void testImeSubtypeListComparator() throws Exception {
+ {
+ final List<ImeSubtypeListItem> items = Arrays.asList(
+ createDummyItem("X", "A", "en_US", 0, "en_US"),
+ createDummyItem("X", "A", "en", 1, "en_US"),
+ createDummyItem("X", "A", "ja", 2, "en_US"),
+ createDummyItem("X", "Z", "en_US", 3, "en_US"),
+ createDummyItem("X", "Z", "en", 4, "en_US"),
+ createDummyItem("X", "Z", "ja", 5, "en_US"),
+ createDummyItem("X", "", "en_US", 6, "en_US"),
+ createDummyItem("X", "", "en", 7, "en_US"),
+ createDummyItem("X", "", "ja", 8, "en_US"),
+ createDummyItem("Y", "A", "en_US", 9, "en_US"),
+ createDummyItem("Y", "A", "en", 10, "en_US"),
+ createDummyItem("Y", "A", "ja", 11, "en_US"),
+ createDummyItem("Y", "Z", "en_US", 12, "en_US"),
+ createDummyItem("Y", "Z", "en", 13, "en_US"),
+ createDummyItem("Y", "Z", "ja", 14, "en_US"),
+ createDummyItem("Y", "", "en_US", 15, "en_US"),
+ createDummyItem("Y", "", "en", 16, "en_US"),
+ createDummyItem("Y", "", "ja", 17, "en_US"),
+ createDummyItem("", "A", "en_US", 18, "en_US"),
+ createDummyItem("", "A", "en", 19, "en_US"),
+ createDummyItem("", "A", "ja", 20, "en_US"),
+ createDummyItem("", "Z", "en_US", 21, "en_US"),
+ createDummyItem("", "Z", "en", 22, "en_US"),
+ createDummyItem("", "Z", "ja", 23, "en_US"),
+ createDummyItem("", "", "en_US", 24, "en_US"),
+ createDummyItem("", "", "en", 25, "en_US"),
+ createDummyItem("", "", "ja", 26, "en_US"));
+
+ for (int i = 0; i < items.size(); ++i) {
+ assertEquals(0, items.get(i).compareTo(items.get(i)));
+ for (int j = i + 1; j < items.size(); ++j) {
+ assertTrue(items.get(i).compareTo(items.get(j)) < 0);
+ assertTrue(items.get(j).compareTo(items.get(i)) > 0);
+ }
+ }
+ }
+
+ {
+ // Following two items have the same priority.
+ final ImeSubtypeListItem nonSystemLocale1 =
+ createDummyItem("X", "A", "ja_JP", 0, "en_us");
+ final ImeSubtypeListItem nonSystemLocale2 =
+ createDummyItem("X", "A", "hi_IN", 1, "en_us");
+ assertEquals(0, nonSystemLocale1.compareTo(nonSystemLocale2));
+ assertEquals(0, nonSystemLocale2.compareTo(nonSystemLocale1));
+ }
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/logging/LogBuilderTest.java b/core/tests/coretests/src/com/android/internal/logging/LogBuilderTest.java
index a340559..1c19d88 100644
--- a/core/tests/coretests/src/com/android/internal/logging/LogBuilderTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/LogBuilderTest.java
@@ -107,11 +107,21 @@
assertEquals(123.0F, out[7]);
}
- public void testCategoryDefault() {
+ public void testCategoryDefault() {
LogBuilder builder = new LogBuilder(10);
Object[] out = builder.serialize();
assertEquals(MetricsEvent.RESERVED_FOR_LOGBUILDER_CATEGORY, out[0]);
assertEquals(10, out[1]);
}
+ public void testGiantLogOmitted() {
+ LogBuilder badBuilder = new LogBuilder(0);
+ StringBuilder b = new StringBuilder();
+ for (int i = 0; i < 4000; i++) {
+ b.append("test, " + i);
+ }
+ badBuilder.addTaggedData(100, b.toString());
+ assertTrue(badBuilder.serialize().length < LogBuilder.MAX_SERIALIZED_SIZE);
+ }
+
}
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 432e77c..a76a328 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -192,5 +192,7 @@
oneway void releasePlayer(in int piid);
+ void disableRingtoneSync();
+
// WARNING: read warning at top of file, it is recommended to add new methods at the end
}
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
index 7614999..8a1027b 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -33,8 +33,11 @@
import android.media.MediaScannerConnection.MediaScannerConnectionClient;
import android.net.Uri;
import android.os.Environment;
+import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.MediaStore;
@@ -850,6 +853,18 @@
public static void setActualDefaultRingtoneUri(Context context, int type, Uri ringtoneUri) {
final ContentResolver resolver = context.getContentResolver();
+ if (Settings.Secure.getString(resolver, Settings.Secure.SYNC_PARENT_SOUNDS).equals("1")) {
+ // Sync is enabled, so we need to disable it
+ IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
+ IAudioService audioService = IAudioService.Stub.asInterface(b);
+ try {
+ audioService.disableRingtoneSync();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to disable ringtone sync.");
+ return;
+ }
+ }
+
String setting = getSettingForType(type);
if (setting == null) return;
if(!isInternalRingtoneUri(ringtoneUri)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 6ac5cb8..49d2462 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -24,6 +24,7 @@
import android.app.INotificationManager;
import android.app.KeyguardManager;
import android.app.Notification;
+import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.RemoteInput;
@@ -1038,6 +1039,7 @@
private void bindGuts(final ExpandableNotificationRow row) {
row.inflateGuts();
final StatusBarNotification sbn = row.getStatusBarNotification();
+ final NotificationChannel channel = row.getEntry().channel;
PackageManager pmUser = getPackageManagerForUser(mContext, sbn.getUser().getIdentifier());
row.setTag(sbn.getPackageName());
final NotificationGuts guts = row.getGuts();
@@ -1077,8 +1079,8 @@
closeControls(row, guts, v);
}
};
- guts.bindNotification(pmUser, iNotificationManager, sbn, onSettingsClick, onDoneClick,
- mNonBlockablePkgs);
+ guts.bindNotification(pmUser, iNotificationManager, sbn, channel,
+ onSettingsClick, onDoneClick, mNonBlockablePkgs);
}
private void closeControls(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index 458daf1..3a89186 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar;
import android.app.Notification;
+import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.graphics.drawable.Icon;
@@ -55,6 +56,7 @@
private static final int COLOR_INVALID = 1;
public String key;
public StatusBarNotification notification;
+ public NotificationChannel channel;
public StatusBarIconView icon;
public StatusBarIconView expandedIcon;
public ExpandableNotificationRow row; // the outer expanded view
@@ -429,6 +431,14 @@
return null;
}
+ public NotificationChannel getChannel(String key) {
+ if (mRankingMap != null) {
+ mRankingMap.getRanking(key, mTmpRanking);
+ return mTmpRanking.getChannel();
+ }
+ return null;
+ }
+
private void updateRankingAndSort(RankingMap ranking) {
if (ranking != null) {
mRankingMap = ranking;
@@ -442,6 +452,7 @@
entry.notification.setOverrideGroupKey(overrideGroupKey);
mGroupManager.onEntryUpdated(entry, oldSbn);
}
+ entry.channel = getChannel(entry.key);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
index c7adb60..83104e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
@@ -72,11 +72,7 @@
private INotificationManager mINotificationManager;
private int mStartingUserImportance;
private StatusBarNotification mStatusBarNotification;
-
- private ImageView mAutoButton;
- private TextView mImportanceSummary;
- private TextView mImportanceTitle;
- private boolean mAuto;
+ private NotificationChannel mNotificationChannel;
private View mImportanceGroup;
private View mChannelDisabled;
@@ -170,11 +166,12 @@
}
void bindNotification(final PackageManager pm, final INotificationManager iNotificationManager,
- final StatusBarNotification sbn, OnSettingsClickListener onSettingsClick,
+ final StatusBarNotification sbn, final NotificationChannel channel,
+ OnSettingsClickListener onSettingsClick,
OnClickListener onDoneClick, final Set<String> nonBlockablePkgs) {
mINotificationManager = iNotificationManager;
+ mNotificationChannel = channel;
mStatusBarNotification = sbn;
- final NotificationChannel channel = sbn.getNotificationChannel();
mStartingUserImportance = channel.getImportance();
final String pkg = sbn.getPackageName();
@@ -288,14 +285,13 @@
if (selectedImportance == mStartingUserImportance) {
return;
}
- final NotificationChannel channel = mStatusBarNotification.getNotificationChannel();
MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE,
selectedImportance - mStartingUserImportance);
- channel.setImportance(selectedImportance);
+ mNotificationChannel.setImportance(selectedImportance);
try {
mINotificationManager.updateNotificationChannelForPackage(
mStatusBarNotification.getPackageName(), mStatusBarNotification.getUid(),
- channel);
+ mNotificationChannel);
} catch (RemoteException e) {
// :(
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 3291d59..9612db0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -1388,7 +1388,7 @@
newNotification.headsUpContentView = sbn.getNotification().headsUpContentView;
StatusBarNotification newSbn = new StatusBarNotification(sbn.getPackageName(),
- sbn.getOpPkg(), sbn.getNotificationChannel(),
+ sbn.getOpPkg(),
sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(),
newNotification, sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationGutsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationGutsTest.java
index c65f7150..cac0806 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationGutsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationGutsTest.java
@@ -91,7 +91,6 @@
// mMockStatusBarNotification with a test channel.
mNotificationChannel = new NotificationChannel(
TEST_CHANNEL, TEST_CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW);
- when(mMockStatusBarNotification.getNotificationChannel()).thenReturn(mNotificationChannel);
when(mMockStatusBarNotification.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
}
@@ -100,7 +99,7 @@
public void testBindNotification_SetsTextApplicationName() throws Exception {
when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
final TextView textView = (TextView) mNotificationGuts.findViewById(R.id.pkgname);
assertTrue(textView.getText().toString().contains("App Name"));
}
@@ -109,7 +108,7 @@
@UiThreadTest
public void testBindNotification_SetsTextChannelName() throws Exception {
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
final TextView textView = (TextView) mNotificationGuts.findViewById(R.id.channel_name);
assertEquals(TEST_CHANNEL_NAME, textView.getText());
}
@@ -119,8 +118,8 @@
public void testBindNotification_SetsOnClickListenerForSettings() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, (View v, int appUid) -> { latch.countDown(); },
- null, null);
+ mMockStatusBarNotification, mNotificationChannel,
+ (View v, int appUid) -> { latch.countDown(); }, null, null);
final TextView settingsButton =
(TextView) mNotificationGuts.findViewById(R.id.more_settings);
@@ -134,7 +133,7 @@
public void testBindNotification_SetsOnClickListenerForDone() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null,
+ mMockStatusBarNotification, mNotificationChannel, null,
(View v) -> { latch.countDown(); },
null);
@@ -148,7 +147,7 @@
@UiThreadTest
public void testHasImportanceChanged_DefaultsToFalse() throws Exception {
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
assertFalse(mNotificationGuts.hasImportanceChanged());
}
@@ -157,7 +156,7 @@
public void testHasImportanceChanged_ReturnsTrueAfterButtonChecked() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
// Find the high button and check it.
RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
highButton.setChecked(true);
@@ -169,7 +168,7 @@
public void testImportanceButtonCheckedBasedOnInitialImportance() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_HIGH);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
assertTrue(highButton.isChecked());
@@ -179,7 +178,7 @@
@UiThreadTest
public void testBindNotification_DoesNotUpdateNotificationChannel() throws Exception {
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), anyInt(), any());
}
@@ -189,7 +188,7 @@
public void testDoesNotUpdateNotificationChannelAfterImportanceChanged() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
highButton.setChecked(true);
@@ -201,7 +200,7 @@
@UiThreadTest
public void testCloseControls_DoesNotUpdateNotificationChannelIfUnchanged() throws Exception {
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
mNotificationGuts.closeControls(-1, -1, true);
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
@@ -213,7 +212,7 @@
public void testCloseControls_DoesNotUpdateNotificationChannelIfUnspecified() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_UNSPECIFIED);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
mNotificationGuts.closeControls(-1, -1, true);
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
@@ -225,7 +224,7 @@
public void testCloseControls_CallsUpdateNotificationChannelIfChanged() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
highButton.setChecked(true);
@@ -240,7 +239,7 @@
public void testCloseControls_DoesNotUpdateNotificationChannelIfSaveFalse() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
highButton.setChecked(true);
@@ -254,7 +253,7 @@
public void testEnabledSwitchOnByDefault() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
assertTrue(enabledSwitch.isChecked());
@@ -265,7 +264,7 @@
public void testEnabledSwitchVisibleByDefault() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
assertEquals(View.VISIBLE, enabledSwitch.getVisibility());
@@ -276,7 +275,8 @@
public void testEnabledSwitchInvisibleIfNonBlockable() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, Collections.singleton(TEST_PACKAGE_NAME));
+ mMockStatusBarNotification, mNotificationChannel, null, null,
+ Collections.singleton(TEST_PACKAGE_NAME));
Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
assertEquals(View.INVISIBLE, enabledSwitch.getVisibility());
@@ -287,7 +287,8 @@
public void testEnabledSwitchChangedCallsUpdateNotificationChannel() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, Collections.singleton(TEST_PACKAGE_NAME));
+ mMockStatusBarNotification, mNotificationChannel, null, null,
+ Collections.singleton(TEST_PACKAGE_NAME));
Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
enabledSwitch.setChecked(false);
@@ -301,7 +302,7 @@
public void testEnabledSwitchOverridesOtherButtons() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
RadioButton lowButton = (RadioButton) mNotificationGuts.findViewById(R.id.low_importance);
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 88bc99f..96c468c 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -3311,6 +3311,16 @@
// OS: 8.0
MANAGE_EXTERNAL_SOURCES = 808;
+ // ACTION: Logged when terms activity finishes.
+ // TIME: Indicates time taken by terms activity to finish in MS.
+ PROVISIONING_TERMS_ACTIVITY_TIME_MS = 809;
+
+ // Indicates number of terms displayed on the terms screen.
+ PROVISIONING_TERMS_COUNT = 810;
+
+ // Indicates number of terms read on the terms screen.
+ PROVISIONING_TERMS_READ = 811;
+
// ---- End O Constants, all O constants go above this line ----
// Add new aosp constants above this line.
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index df5f01d..49423b9 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -6502,6 +6502,35 @@
return mRecordMonitor.getActiveRecordingConfigurations();
}
+ public void disableRingtoneSync() {
+ final int callingUserId = UserHandle.getCallingUserId();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ UserManager userManager = UserManager.get(mContext);
+
+ // Disable the sync setting
+ Settings.Secure.putIntForUser(mContentResolver,
+ Settings.Secure.SYNC_PARENT_SOUNDS, 0 /* false */, callingUserId);
+
+ UserInfo parentInfo = userManager.getProfileParent(callingUserId);
+ if (parentInfo != null && parentInfo.id != callingUserId) {
+ // This is a managed profile, so we clone the ringtones from the parent profile
+ cloneRingtoneSetting(callingUserId, parentInfo.id, Settings.System.RINGTONE);
+ cloneRingtoneSetting(callingUserId, parentInfo.id,
+ Settings.System.NOTIFICATION_SOUND);
+ cloneRingtoneSetting(callingUserId, parentInfo.id, Settings.System.ALARM_ALERT);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private void cloneRingtoneSetting(int userId, int parentId, String ringtoneSetting) {
+ String parentSetting = Settings.System.getStringForUser(mContentResolver, ringtoneSetting,
+ parentId);
+ Settings.System.putStringForUser(mContentResolver, ringtoneSetting, parentSetting, userId);
+ }
+
//======================
// Audio playback notification
//======================
diff --git a/services/core/java/com/android/server/notification/BadgeExtractor.java b/services/core/java/com/android/server/notification/BadgeExtractor.java
new file mode 100644
index 0000000..4795fbf
--- /dev/null
+++ b/services/core/java/com/android/server/notification/BadgeExtractor.java
@@ -0,0 +1,59 @@
+/**
+* Copyright (C) 2017 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.notification;
+
+import android.content.Context;
+import android.util.Slog;
+
+/**
+ * Determines whether a badge should be shown for this notification
+ */
+public class BadgeExtractor implements NotificationSignalExtractor {
+ private static final String TAG = "BadgeExtractor";
+ private static final boolean DBG = false;
+
+ private RankingConfig mConfig;
+
+ public void initialize(Context ctx, NotificationUsageStats usageStats) {
+ if (DBG) Slog.d(TAG, "Initializing " + getClass().getSimpleName() + ".");
+ }
+
+ public RankingReconsideration process(NotificationRecord record) {
+ if (record == null || record.getNotification() == null) {
+ if (DBG) Slog.d(TAG, "skipping empty notification");
+ return null;
+ }
+
+ if (mConfig == null) {
+ if (DBG) Slog.d(TAG, "missing config");
+ return null;
+ }
+ boolean appCanShowBadge =
+ mConfig.canShowBadge(record.sbn.getPackageName(), record.sbn.getUid());
+ if (!appCanShowBadge) {
+ record.setShowBadge(false);
+ } else {
+ record.setShowBadge(record.getChannel().canShowBadge() && appCanShowBadge);
+ }
+
+ return null;
+ }
+
+ @Override
+ public void setConfig(RankingConfig config) {
+ mConfig = config;
+ }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 168884d..96459be 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1243,6 +1243,35 @@
sendRegisteredOnlyBroadcast(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED);
}
+ private void updateNotificationChannelInt(String pkg, int uid, NotificationChannel channel,
+ boolean fromAssistant) {
+ if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) {
+ // cancel
+ cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0, true,
+ UserHandle.getUserId(Binder.getCallingUid()), REASON_CHANNEL_BANNED,
+ null);
+ }
+ if (fromAssistant) {
+ mRankingHelper.updateNotificationChannelFromAssistant(pkg, uid, channel);
+ } else {
+ mRankingHelper.updateNotificationChannel(pkg, uid, channel);
+ }
+
+ synchronized (mNotificationList) {
+ final int N = mNotificationList.size();
+ for (int i = N - 1; i >= 0; --i) {
+ NotificationRecord r = mNotificationList.get(i);
+ if (channel.getId() != null && channel.getId().equals(r.getChannel().getId())) {
+ r.updateNotificationChannel(mRankingHelper.getNotificationChannel(
+ r.sbn.getPackageName(), r.getUser().getIdentifier(),
+ channel.getId(), false));
+ }
+ }
+ }
+ mRankingHandler.requestSort(true);
+ savePolicyFile();
+ }
+
private ArrayList<ComponentName> getSuppressors() {
ArrayList<ComponentName> names = new ArrayList<ComponentName>();
for (int i = mListenersDisablingEffects.size() - 1; i >= 0; --i) {
@@ -1521,6 +1550,19 @@
}
@Override
+ public boolean canShowBadge(String pkg, int uid) {
+ checkCallerIsSystem();
+ return mRankingHelper.canShowBadge(pkg, uid);
+ }
+
+ @Override
+ public void setShowBadge(String pkg, int uid, boolean showBadge) {
+ checkCallerIsSystem();
+ mRankingHelper.setShowBadge(pkg, uid, showBadge);
+ savePolicyFile();
+ }
+
+ @Override
public void createNotificationChannels(String pkg,
ParceledListSlice channelsList) throws RemoteException {
checkCallerIsSystemOrSameApp(pkg);
@@ -1566,15 +1608,8 @@
public void updateNotificationChannelForPackage(String pkg, int uid,
NotificationChannel channel) {
enforceSystemOrSystemUI("Caller not system or systemui");
- if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) {
- // cancel
- cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0, true,
- UserHandle.getUserId(Binder.getCallingUid()), REASON_CHANNEL_BANNED,
- null);
- }
- mRankingHelper.updateNotificationChannel(pkg, uid, channel);
- mRankingHandler.requestSort(true);
- savePolicyFile();
+ Preconditions.checkNotNull(channel);
+ updateNotificationChannelInt(pkg, uid, channel, false);
}
@Override
@@ -1701,7 +1736,6 @@
return new StatusBarNotification(
sbn.getPackageName(),
sbn.getOpPkg(),
- sbn.getNotificationChannel(),
sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(),
sbn.getNotification().clone(),
sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime());
@@ -2495,15 +2529,9 @@
public void updateNotificationChannelFromAssistant(INotificationListener token, String pkg,
NotificationChannel channel) throws RemoteException {
ManagedServiceInfo info = mNotificationAssistants.checkServiceTokenLocked(token);
- if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) {
- // cancel
- cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0, true,
- info.userid, REASON_CHANNEL_BANNED, null);
- }
+ Preconditions.checkNotNull(channel);
int uid = mPackageManager.getPackageUid(pkg, 0, info.userid);
- mRankingHelper.updateNotificationChannelFromAssistant(pkg, uid, channel);
- mRankingHandler.requestSort(true);
- savePolicyFile();
+ updateNotificationChannelInt(pkg, uid, channel, true);
}
@Override
@@ -2528,7 +2556,7 @@
final ArrayList<SnoozeCriterion> snoozeCriterionList =
adjustment.getSignals().getParcelableArrayList(Adjustment.KEY_SNOOZE_CRITERIA);
if (!TextUtils.isEmpty(overrideChannelId)) {
- n.setNotificationChannelOverride(mRankingHelper.getNotificationChannel(
+ n.updateNotificationChannel(mRankingHelper.getNotificationChannel(
n.sbn.getPackageName(), n.sbn.getUid(), overrideChannelId,
false /* includeDeleted */));
}
@@ -2610,13 +2638,13 @@
final StatusBarNotification summarySbn =
new StatusBarNotification(adjustedSbn.getPackageName(),
adjustedSbn.getOpPkg(),
- adjustedSbn.getNotificationChannel(),
Integer.MAX_VALUE,
GroupHelper.AUTOGROUP_KEY, adjustedSbn.getUid(),
adjustedSbn.getInitialPid(), summaryNotification,
adjustedSbn.getUser(), GroupHelper.AUTOGROUP_KEY,
System.currentTimeMillis());
- summaryRecord = new NotificationRecord(getContext(), summarySbn);
+ summaryRecord = new NotificationRecord(getContext(), summarySbn,
+ notificationRecord.getChannel());
summaries.put(pkg, summarySbn.getKey());
}
}
@@ -2882,7 +2910,7 @@
final NotificationChannel channel = mRankingHelper.getNotificationChannelWithFallback(pkg,
callingUid, notification.getChannel(), false /* includeDeleted */);
final StatusBarNotification n = new StatusBarNotification(
- pkg, opPkg, channel, id, tag, callingUid, callingPid, notification,
+ pkg, opPkg, id, tag, callingUid, callingPid, notification,
user, null, System.currentTimeMillis());
// Limit the number of notifications that any given package except the android
@@ -2946,7 +2974,7 @@
Notification.PRIORITY_MAX);
// setup local book-keeping
- final NotificationRecord r = new NotificationRecord(getContext(), n);
+ final NotificationRecord r = new NotificationRecord(getContext(), n, channel);
synchronized (mNotificationLock) {
mEnqueuedNotifications.add(r);
}
@@ -3490,14 +3518,18 @@
boolean forceUpdate = ((Boolean) msg.obj == null) ? false : (boolean) msg.obj;
synchronized (mNotificationLock) {
final int N = mNotificationList.size();
+ // Any field that can change via one of the extractors or by the assistant
+ // needs to be added here.
ArrayList<String> orderBefore = new ArrayList<String>(N);
ArrayList<String> groupOverrideBefore = new ArrayList<>(N);
int[] visibilities = new int[N];
+ boolean[] showBadges = new boolean[N];
for (int i = 0; i < N; i++) {
final NotificationRecord r = mNotificationList.get(i);
orderBefore.add(r.getKey());
groupOverrideBefore.add(r.sbn.getGroupKey());
visibilities[i] = r.getPackageVisibilityOverride();
+ showBadges[i] = r.canShowBadge();
mRankingHelper.extractSignals(r);
}
mRankingHelper.sort(mNotificationList);
@@ -3506,7 +3538,8 @@
if (forceUpdate
|| !orderBefore.get(i).equals(r.getKey())
|| visibilities[i] != r.getPackageVisibilityOverride()
- || !groupOverrideBefore.get(i).equals(r.sbn.getGroupKey())) {
+ || !groupOverrideBefore.get(i).equals(r.sbn.getGroupKey())
+ || showBadges[i] != r.canShowBadge()) {
scheduleSendRankingUpdate();
return;
}
@@ -4246,9 +4279,10 @@
Bundle visibilityOverrides = new Bundle();
Bundle suppressedVisualEffects = new Bundle();
Bundle explanation = new Bundle();
- Bundle overrideChannels = new Bundle();
+ Bundle channels = new Bundle();
Bundle overridePeople = new Bundle();
Bundle snoozeCriteria = new Bundle();
+ Bundle showBadge = new Bundle();
for (int i = 0; i < N; i++) {
NotificationRecord record = mNotificationList.get(i);
if (!isVisibleToListener(record.sbn, info)) {
@@ -4270,9 +4304,10 @@
visibilityOverrides.putInt(key, record.getPackageVisibilityOverride());
}
overrideGroupKeys.putString(key, record.sbn.getOverrideGroupKey());
- overrideChannels.putParcelable(key, record.getChannel());
+ channels.putParcelable(key, record.getChannel());
overridePeople.putStringArrayList(key, record.getPeopleOverride());
snoozeCriteria.putParcelableArrayList(key, record.getSnoozeCriteria());
+ showBadge.putBoolean(key, record.canShowBadge());
}
final int M = keys.size();
String[] keysAr = keys.toArray(new String[M]);
@@ -4283,7 +4318,7 @@
}
return new NotificationRankingUpdate(keysAr, interceptedKeysAr, visibilityOverrides,
suppressedVisualEffects, importanceAr, explanation, overrideGroupKeys,
- overrideChannels, overridePeople, snoozeCriteria);
+ channels, overridePeople, snoozeCriteria, showBadge);
}
private boolean isVisibleToListener(StatusBarNotification sbn, ManagedServiceInfo listener) {
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index e8c3d97..2a5a25f 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -25,7 +25,6 @@
import android.app.NotificationChannel;
import android.content.Context;
import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -114,12 +113,14 @@
private Uri mSound;
private long[] mVibration;
private AudioAttributes mAttributes;
- private NotificationChannel mOverrideChannel;
+ private NotificationChannel mChannel;
private ArrayList<String> mPeopleOverride;
private ArrayList<SnoozeCriterion> mSnoozeCriteria;
+ private boolean mShowBadge;
@VisibleForTesting
- public NotificationRecord(Context context, StatusBarNotification sbn)
+ public NotificationRecord(Context context, StatusBarNotification sbn,
+ NotificationChannel channel)
{
this.sbn = sbn;
mOriginalFlags = sbn.getNotification().flags;
@@ -128,6 +129,7 @@
mUpdateTimeMs = mCreationTimeMs;
mContext = context;
stats = new NotificationUsageStats.SingleNotificationStats();
+ mChannel = channel;
mPreChannelsNotification = isPreChannelsNotification();
mSound = calculateSound();
mVibration = calculateVibration();
@@ -154,7 +156,7 @@
private Uri calculateSound() {
final Notification n = sbn.getNotification();
- Uri sound = sbn.getNotificationChannel().getSound();
+ Uri sound = mChannel.getSound();
if (mPreChannelsNotification && (getChannel().getUserLockedFields()
& NotificationChannel.USER_LOCKED_SOUND) == 0) {
@@ -386,7 +388,8 @@
pw.println(prefix + " mSound= " + mSound);
pw.println(prefix + " mVibration= " + mVibration);
pw.println(prefix + " mAttributes= " + mAttributes);
- pw.println(prefix + " overrideChannel=" + getChannel());
+ pw.println(prefix + " mShowBadge=" + mShowBadge);
+ pw.println(prefix + " channel=" + getChannel());
if (getPeopleOverride() != null) {
pw.println(prefix + " overridePeople= " + TextUtils.join(",", getPeopleOverride()));
}
@@ -640,16 +643,24 @@
}
public NotificationChannel getChannel() {
- return mOverrideChannel == null ? sbn.getNotificationChannel() : mOverrideChannel;
+ return mChannel;
}
- protected void setNotificationChannelOverride(NotificationChannel channel) {
- mOverrideChannel = channel;
- if (mOverrideChannel != null) {
+ protected void updateNotificationChannel(NotificationChannel channel) {
+ if (channel != null) {
+ mChannel = channel;
calculateImportance();
}
}
+ public void setShowBadge(boolean showBadge) {
+ mShowBadge = showBadge;
+ }
+
+ public boolean canShowBadge() {
+ return mShowBadge;
+ }
+
public Uri getSound() {
return mSound;
}
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index c2cef09..492d5c6 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -22,6 +22,8 @@
void setImportance(String packageName, int uid, int importance);
int getImportance(String packageName, int uid);
+ void setShowBadge(String packageName, int uid, boolean showBadge);
+ boolean canShowBadge(String packageName, int uid);
void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
boolean fromTargetApp);
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index e44fb7f..1861bcb 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -67,10 +67,12 @@
private static final String ATT_PRIORITY = "priority";
private static final String ATT_VISIBILITY = "visibility";
private static final String ATT_IMPORTANCE = "importance";
+ private static final String ATT_SHOW_BADGE = "show_badge";
private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE;
private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED;
+ private static final boolean DEFAULT_SHOW_BADGE = true;
private final NotificationSignalExtractor[] mSignalExtractors;
private final NotificationComparator mPreliminaryComparator;
@@ -169,7 +171,8 @@
Record r = getOrCreateRecord(name, uid,
safeInt(parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE),
safeInt(parser, ATT_PRIORITY, DEFAULT_PRIORITY),
- safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY));
+ safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY),
+ safeBool(parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE));
// Channels
final int innerDepth = parser.getDepth();
@@ -215,11 +218,11 @@
private Record getOrCreateRecord(String pkg, int uid) {
return getOrCreateRecord(pkg, uid,
- DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY);
+ DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE);
}
private Record getOrCreateRecord(String pkg, int uid, int importance, int priority,
- int visibility) {
+ int visibility, boolean showBadge) {
final String key = recordKey(pkg, uid);
Record r = (uid == Record.UNKNOWN_UID) ? mRestoredWithoutUids.get(pkg) : mRecords.get(key);
if (r == null) {
@@ -229,6 +232,7 @@
r.importance = importance;
r.priority = priority;
r.visibility = visibility;
+ r.showBadge = showBadge;
createDefaultChannelIfMissing(r);
if (r.uid == Record.UNKNOWN_UID) {
mRestoredWithoutUids.put(pkg, r);
@@ -298,7 +302,7 @@
}
final boolean hasNonDefaultSettings = r.importance != DEFAULT_IMPORTANCE
|| r.priority != DEFAULT_PRIORITY || r.visibility != DEFAULT_VISIBILITY
- || r.channels.size() > 0;
+ || r.showBadge != DEFAULT_SHOW_BADGE || r.channels.size() > 0;
if (hasNonDefaultSettings) {
out.startTag(null, TAG_PACKAGE);
out.attribute(null, ATT_NAME, r.pkg);
@@ -311,6 +315,7 @@
if (r.visibility != DEFAULT_VISIBILITY) {
out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility));
}
+ out.attribute(null, ATT_SHOW_BADGE, Boolean.toString(r.showBadge));
if (!forBackup) {
out.attribute(null, ATT_UID, Integer.toString(r.uid));
@@ -396,6 +401,12 @@
return Collections.binarySearch(notificationList, target, mFinalComparator);
}
+ private static boolean safeBool(XmlPullParser parser, String att, boolean defValue) {
+ final String value = parser.getAttributeValue(null, att);
+ if (TextUtils.isEmpty(value)) return defValue;
+ return Boolean.parseBoolean(value);
+ }
+
private static int safeInt(XmlPullParser parser, String att, int defValue) {
final String val = parser.getAttributeValue(null, att);
return tryParseInt(val, defValue);
@@ -419,6 +430,17 @@
}
@Override
+ public boolean canShowBadge(String packageName, int uid) {
+ return getOrCreateRecord(packageName, uid).showBadge;
+ }
+
+ @Override
+ public void setShowBadge(String packageName, int uid, boolean showBadge) {
+ getOrCreateRecord(packageName, uid).showBadge = showBadge;
+ updateConfig();
+ }
+
+ @Override
public void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
boolean fromTargetApp) {
Preconditions.checkNotNull(pkg);
@@ -454,6 +476,9 @@
if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
channel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE);
}
+ if (!r.showBadge) {
+ channel.setShowBadge(false);
+ }
r.channels.put(channel.getId(), channel);
updateConfig();
}
@@ -672,7 +697,7 @@
final Record r = records.valueAt(i);
if (filter == null || filter.matches(r.pkg)) {
pw.print(prefix);
- pw.print(" ");
+ pw.print(" AppSettings: ");
pw.print(r.pkg);
pw.print(" (");
pw.print(r.uid == Record.UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid));
@@ -689,6 +714,8 @@
pw.print(" visibility=");
pw.print(Notification.visibilityToString(r.visibility));
}
+ pw.print(" showBadge=");
+ pw.print(Boolean.toString(r.showBadge));
pw.println();
for (NotificationChannel channel : r.channels.values()) {
pw.print(prefix);
@@ -725,6 +752,9 @@
if (r.visibility != DEFAULT_VISIBILITY) {
record.put("visibility", Notification.visibilityToString(r.visibility));
}
+ if (r.showBadge != DEFAULT_SHOW_BADGE) {
+ record.put("showBadge", Boolean.valueOf(r.showBadge));
+ }
for (NotificationChannel channel : r.channels.values()) {
record.put("channel", channel.toJson());
}
@@ -838,6 +868,7 @@
int importance = DEFAULT_IMPORTANCE;
int priority = DEFAULT_PRIORITY;
int visibility = DEFAULT_VISIBILITY;
+ boolean showBadge = DEFAULT_SHOW_BADGE;
ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
}
diff --git a/services/tests/notification/src/com/android/server/notification/BadgeExtractorTest.java b/services/tests/notification/src/com/android/server/notification/BadgeExtractorTest.java
new file mode 100644
index 0000000..b26bac3
--- /dev/null
+++ b/services/tests/notification/src/com/android/server/notification/BadgeExtractorTest.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2017 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.notification;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.Notification;
+import android.app.Notification.Builder;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BadgeExtractorTest {
+
+ @Mock RankingConfig mConfig;
+
+ private String mPkg = "com.android.server.notification";
+ private int mId = 1001;
+ private String mTag = null;
+ private int mUid = 1000;
+ private int mPid = 2000;
+ private UserHandle mUser = UserHandle.of(ActivityManager.getCurrentUser());
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ private NotificationRecord getNotificationRecord(NotificationChannel channel) {
+ final Builder builder = new Builder(getContext())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setPriority(Notification.PRIORITY_HIGH)
+ .setDefaults(Notification.DEFAULT_SOUND);
+
+ Notification n = builder.build();
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, mId, mTag, mUid,
+ mPid, n, mUser, null, System.currentTimeMillis());
+ NotificationRecord r = new NotificationRecord(getContext(), sbn, channel);
+ return r;
+ }
+
+ private Context getContext() {
+ return InstrumentationRegistry.getTargetContext();
+ }
+
+ //
+ // Tests
+ //
+
+ @Test
+ public void testAppYesChannelNo() throws Exception {
+ BadgeExtractor extractor = new BadgeExtractor();
+ extractor.setConfig(mConfig);
+
+ when(mConfig.canShowBadge(mPkg, mUid)).thenReturn(true);
+ NotificationChannel channel =
+ new NotificationChannel("a", "a", NotificationManager.IMPORTANCE_UNSPECIFIED);
+ channel.setShowBadge(false);
+
+ NotificationRecord r = getNotificationRecord(channel);
+
+ extractor.process(r);
+
+ assertFalse(r.canShowBadge());
+ }
+
+ @Test
+ public void testAppNoChannelYes() throws Exception {
+ BadgeExtractor extractor = new BadgeExtractor();
+ extractor.setConfig(mConfig);
+
+ when(mConfig.canShowBadge(mPkg, mUid)).thenReturn(false);
+ NotificationChannel channel =
+ new NotificationChannel("a", "a", NotificationManager.IMPORTANCE_HIGH);
+ channel.setShowBadge(true);
+
+ NotificationRecord r = getNotificationRecord(channel);
+
+ extractor.process(r);
+
+ assertFalse(r.canShowBadge());
+ }
+
+ @Test
+ public void testAppYesChannelYes() throws Exception {
+ BadgeExtractor extractor = new BadgeExtractor();
+ extractor.setConfig(mConfig);
+
+ when(mConfig.canShowBadge(mPkg, mUid)).thenReturn(true);
+ NotificationChannel channel =
+ new NotificationChannel("a", "a", NotificationManager.IMPORTANCE_UNSPECIFIED);
+ channel.setShowBadge(true);
+
+ NotificationRecord r = getNotificationRecord(channel);
+
+ extractor.process(r);
+
+ assertTrue(r.canShowBadge());
+ }
+
+ @Test
+ public void testAppNoChannelNo() throws Exception {
+ BadgeExtractor extractor = new BadgeExtractor();
+ extractor.setConfig(mConfig);
+
+ when(mConfig.canShowBadge(mPkg, mUid)).thenReturn(false);
+ NotificationChannel channel =
+ new NotificationChannel("a", "a", NotificationManager.IMPORTANCE_UNSPECIFIED);
+ channel.setShowBadge(false);
+
+ NotificationRecord r = getNotificationRecord(channel);
+
+ extractor.process(r);
+
+ assertFalse(r.canShowBadge());
+ }
+}
diff --git a/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
index ad436724a..468a26b 100644
--- a/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -230,9 +230,9 @@
n.flags |= Notification.FLAG_INSISTENT;
}
- StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, channel, id, mTag, mUid,
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, id, mTag, mUid,
mPid, n, mUser, null, System.currentTimeMillis());
- NotificationRecord r = new NotificationRecord(getContext(), sbn);
+ NotificationRecord r = new NotificationRecord(getContext(), sbn, channel);
mService.addNotification(r);
return r;
}
diff --git a/services/tests/notification/src/com/android/server/notification/GroupHelperTest.java b/services/tests/notification/src/com/android/server/notification/GroupHelperTest.java
index f48d785..936531b 100644
--- a/services/tests/notification/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/GroupHelperTest.java
@@ -70,9 +70,7 @@
if (groupKey != null) {
nb.setGroup(groupKey);
}
- NotificationChannel channel =
- new NotificationChannel("test", "test", NotificationManager.IMPORTANCE_LOW);
- return new StatusBarNotification(pkg, pkg, channel, id, tag, 0, 0, nb.build(), user, null,
+ return new StatusBarNotification(pkg, pkg, id, tag, 0, 0, nb.build(), user, null,
System.currentTimeMillis());
}
diff --git a/services/tests/notification/src/com/android/server/notification/ImportanceExtractorTest.java b/services/tests/notification/src/com/android/server/notification/ImportanceExtractorTest.java
index eee9cf1..f8a32bb 100644
--- a/services/tests/notification/src/com/android/server/notification/ImportanceExtractorTest.java
+++ b/services/tests/notification/src/com/android/server/notification/ImportanceExtractorTest.java
@@ -69,9 +69,9 @@
.setDefaults(Notification.DEFAULT_SOUND);
Notification n = builder.build();
- StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, channel, mId, mTag, mUid,
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, mId, mTag, mUid,
mPid, n, mUser, null, System.currentTimeMillis());
- NotificationRecord r = new NotificationRecord(getContext(), sbn);
+ NotificationRecord r = new NotificationRecord(getContext(), sbn, channel);
return r;
}
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/notification/src/com/android/server/notification/NotificationComparatorTest.java
index 403b65c..aa08b41 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationComparatorTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationComparatorTest.java
@@ -105,8 +105,8 @@
.setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
.build();
mRecordMinCall = new NotificationRecord(mContext, new StatusBarNotification(callPkg,
- callPkg, getDefaultChannel(), 1, "minCall", callUid, callUid, n1,
- new UserHandle(userId), "", 2000));
+ callPkg, 1, "minCall", callUid, callUid, n1,
+ new UserHandle(userId), "", 2000), getDefaultChannel());
mRecordMinCall.setUserImportance(NotificationManager.IMPORTANCE_MIN);
Notification n2 = new Notification.Builder(mContext)
@@ -114,8 +114,8 @@
.setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
.build();
mRecordHighCall = new NotificationRecord(mContext, new StatusBarNotification(callPkg,
- callPkg, getDefaultChannel(), 1, "highcall", callUid, callUid, n2,
- new UserHandle(userId), "", 1999));
+ callPkg, 1, "highcall", callUid, callUid, n2,
+ new UserHandle(userId), "", 1999), getDefaultChannel());
mRecordHighCall.setUserImportance(NotificationManager.IMPORTANCE_HIGH);
Notification n3 = new Notification.Builder(mContext)
@@ -124,43 +124,43 @@
.setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
.build();
mRecordDefaultMedia = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
- pkg2, getDefaultChannel(), 1, "media", uid2, uid2, n3, new UserHandle(userId),
- "", 1499));
+ pkg2, 1, "media", uid2, uid2, n3, new UserHandle(userId),
+ "", 1499), getDefaultChannel());
mRecordDefaultMedia.setUserImportance(NotificationManager.IMPORTANCE_DEFAULT);
Notification n4 = new Notification.Builder(mContext)
.setStyle(new Notification.MessagingStyle("sender!")).build();
mRecordInlineReply = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
- pkg2, getDefaultChannel(), 1, "inlinereply", uid2, uid2, n4, new UserHandle(userId),
- "", 1599));
+ pkg2, 1, "inlinereply", uid2, uid2, n4, new UserHandle(userId),
+ "", 1599), getDefaultChannel());
mRecordInlineReply.setUserImportance(NotificationManager.IMPORTANCE_HIGH);
mRecordInlineReply.setPackagePriority(Notification.PRIORITY_MAX);
Notification n5 = new Notification.Builder(mContext)
.setCategory(Notification.CATEGORY_MESSAGE).build();
mRecordSms = new NotificationRecord(mContext, new StatusBarNotification(smsPkg,
- smsPkg, getDefaultChannel(), 1, "sms", smsUid, smsUid, n5, new UserHandle(userId),
- "", 1299));
+ smsPkg, 1, "sms", smsUid, smsUid, n5, new UserHandle(userId),
+ "", 1299), getDefaultChannel());
mRecordSms.setUserImportance(NotificationManager.IMPORTANCE_DEFAULT);
Notification n6 = new Notification.Builder(mContext).build();
mRecordStarredContact = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
- pkg2, getDefaultChannel(), 1, "starred", uid2, uid2, n6, new UserHandle(userId),
- "", 1259));
+ pkg2, 1, "starred", uid2, uid2, n6, new UserHandle(userId),
+ "", 1259), getDefaultChannel());
mRecordStarredContact.setContactAffinity(ValidateNotificationPeople.STARRED_CONTACT);
mRecordStarredContact.setUserImportance(NotificationManager.IMPORTANCE_DEFAULT);
Notification n7 = new Notification.Builder(mContext).build();
mRecordContact = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
- pkg2, getDefaultChannel(), 1, "contact", uid2, uid2, n7, new UserHandle(userId),
- "", 1259));
+ pkg2, 1, "contact", uid2, uid2, n7, new UserHandle(userId),
+ "", 1259), getDefaultChannel());
mRecordContact.setContactAffinity(ValidateNotificationPeople.VALID_CONTACT);
mRecordContact.setUserImportance(NotificationManager.IMPORTANCE_DEFAULT);
Notification n8 = new Notification.Builder(mContext).build();
mRecordUrgent = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
- pkg2, getDefaultChannel(), 1, "urgent", uid2, uid2, n8, new UserHandle(userId),
- "", 1258));
+ pkg2, 1, "urgent", uid2, uid2, n8, new UserHandle(userId),
+ "", 1258), getDefaultChannel());
mRecordUrgent.setUserImportance(NotificationManager.IMPORTANCE_HIGH);
Notification n9 = new Notification.Builder(mContext)
@@ -169,15 +169,15 @@
|Notification.FLAG_FOREGROUND_SERVICE, true)
.build();
mRecordCheater = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
- pkg2, getDefaultChannel(), 1, "cheater", uid2, uid2, n9, new UserHandle(userId),
- "", 9258));
+ pkg2, 1, "cheater", uid2, uid2, n9, new UserHandle(userId),
+ "", 9258), getDefaultChannel());
mRecordCheater.setUserImportance(NotificationManager.IMPORTANCE_LOW);
Notification n10 = new Notification.Builder(mContext)
.setStyle(new Notification.InboxStyle().setSummaryText("message!")).build();
mRecordEmail = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
- pkg2, getDefaultChannel(), 1, "email", uid2, uid2, n10, new UserHandle(userId),
- "", 1599));
+ pkg2, 1, "email", uid2, uid2, n10, new UserHandle(userId),
+ "", 1599), getDefaultChannel());
mRecordEmail.setUserImportance(NotificationManager.IMPORTANCE_HIGH);
}
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationListenerServiceTest.java
index b6166f6..f0f4c4d 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -59,6 +59,7 @@
assertEquals(getChannel(key, i), ranking.getChannel());
assertEquals(getPeople(key, i), ranking.getAdditionalPeople());
assertEquals(getSnoozeCriteria(key, i), ranking.getSnoozeCriteria());
+ assertEquals(getShowBadge(i), ranking.canShowBadge());
}
}
@@ -68,9 +69,10 @@
Bundle overrideGroupKeys = new Bundle();
Bundle suppressedVisualEffects = new Bundle();
Bundle explanation = new Bundle();
- Bundle overrideChannels = new Bundle();
+ Bundle channels = new Bundle();
Bundle overridePeople = new Bundle();
Bundle snoozeCriteria = new Bundle();
+ Bundle showBadge = new Bundle();
int[] importance = new int[mKeys.length];
for (int i = 0; i < mKeys.length; i++) {
@@ -83,14 +85,15 @@
suppressedVisualEffects.putInt(key, getSuppressedVisualEffects(i));
importance[i] = getImportance(i);
explanation.putString(key, getExplanation(key));
- overrideChannels.putParcelable(key, getChannel(key, i));
+ channels.putParcelable(key, getChannel(key, i));
overridePeople.putStringArrayList(key, getPeople(key, i));
snoozeCriteria.putParcelableArrayList(key, getSnoozeCriteria(key, i));
+ showBadge.putBoolean(key, getShowBadge(i));
}
NotificationRankingUpdate update = new NotificationRankingUpdate(mKeys,
interceptedKeys.toArray(new String[0]), visibilityOverrides,
suppressedVisualEffects, importance, explanation, overrideGroupKeys,
- overrideChannels, overridePeople, snoozeCriteria);
+ channels, overridePeople, snoozeCriteria, showBadge);
return update;
}
@@ -122,6 +125,10 @@
return new NotificationChannel(key, key, getImportance(index));
}
+ private boolean getShowBadge(int index) {
+ return index % 3 == 0;
+ }
+
private ArrayList<String> getPeople(String key, int index) {
ArrayList<String> people = new ArrayList<>();
for (int i = 0; i < index; i++) {
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
index 9b74fcc..92d9810 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -132,9 +132,9 @@
.setPriority(Notification.PRIORITY_HIGH)
.build();
StatusBarNotification sbn = new StatusBarNotification(mContext.getPackageName(),
- mContext.getPackageName(), channel, 1, "tag", uid, 0,
+ mContext.getPackageName(), 1, "tag", uid, 0,
n, new UserHandle(uid), null, 0);
- return new NotificationRecord(mContext, sbn);
+ return new NotificationRecord(mContext, sbn, channel);
}
@Test
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java
index fc94271f..15dcc26 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java
@@ -136,10 +136,10 @@
Notification n = builder.build();
if (preO) {
- return new StatusBarNotification(pkg, pkg, defaultChannel, id1, tag1, uid, uid, n,
+ return new StatusBarNotification(pkg, pkg, id1, tag1, uid, uid, n,
mUser, null, uid);
} else {
- return new StatusBarNotification(pkg2, pkg2, channel, id2, tag2, uid2, uid2, n,
+ return new StatusBarNotification(pkg2, pkg2, id2, tag2, uid2, uid2, n,
mUser, null, uid2);
}
}
@@ -155,7 +155,7 @@
StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */,
true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, record.getSound());
}
@@ -166,7 +166,7 @@
StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */,
false /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
assertEquals(CUSTOM_SOUND, record.getSound());
}
@@ -178,7 +178,7 @@
StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */,
true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
assertEquals(CUSTOM_SOUND, record.getSound());
}
@@ -189,7 +189,7 @@
StatusBarNotification sbn = getNotification(false /*preO */, true /* noisy */,
true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
assertEquals(CUSTOM_SOUND, record.getSound());
}
@@ -200,7 +200,7 @@
StatusBarNotification sbn = getNotification(true /*preO */, false /* noisy */,
false /* defaultSound */, true /* buzzy */, true /* defaultBuzz */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
assertNotNull(record.getVibration());
}
@@ -211,7 +211,7 @@
StatusBarNotification sbn = getNotification(true /*preO */, false /* noisy */,
false /* defaultSound */, true /* buzzy */, false /* defaultBuzz */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
assertEquals(CUSTOM_VIBRATION, record.getVibration());
}
@@ -223,7 +223,7 @@
StatusBarNotification sbn = getNotification(true /*preO */, false /* noisy */,
false /* defaultSound */, true /* buzzy */, false /* defaultBuzz */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
assertTrue(!Objects.equals(CUSTOM_VIBRATION, record.getVibration()));
}
@@ -234,7 +234,7 @@
StatusBarNotification sbn = getNotification(false /*preO */, false /* noisy */,
false /* defaultSound */, true /* buzzy */, false /* defaultBuzz */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
assertEquals(CUSTOM_CHANNEL_VIBRATION, record.getVibration());
}
@@ -245,7 +245,7 @@
StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */,
false /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
assertEquals(CUSTOM_ATTRIBUTES, record.getAudioAttributes());
}
@@ -256,7 +256,7 @@
StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */,
false /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
assertEquals(CUSTOM_ATTRIBUTES, record.getAudioAttributes());
}
@@ -264,7 +264,7 @@
public void testImportance_preUpgrade() throws Exception {
StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */,
true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
assertEquals(NotificationManager.IMPORTANCE_HIGH, record.getImportance());
}
@@ -275,7 +275,7 @@
StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */,
true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
assertEquals(NotificationManager.IMPORTANCE_LOW, record.getImportance());
}
@@ -283,7 +283,7 @@
public void testImportance_upgrade() throws Exception {
StatusBarNotification sbn = getNotification(false /*preO */, true /* noisy */,
true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
assertEquals(NotificationManager.IMPORTANCE_DEFAULT, record.getImportance());
}
}
diff --git a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
index 59ac427..0320d8a 100644
--- a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
@@ -105,8 +105,8 @@
.setWhen(1205)
.build();
mRecordGroupGSortA = new NotificationRecord(getContext(), new StatusBarNotification(
- "package", "package", getDefaultChannel(), 1, null, 0, 0, mNotiGroupGSortA, user,
- null, System.currentTimeMillis()));
+ "package", "package", 1, null, 0, 0, mNotiGroupGSortA, user,
+ null, System.currentTimeMillis()), getDefaultChannel());
mNotiGroupGSortB = new Notification.Builder(getContext())
.setContentTitle("B")
@@ -115,24 +115,24 @@
.setWhen(1200)
.build();
mRecordGroupGSortB = new NotificationRecord(getContext(), new StatusBarNotification(
- "package", "package", getDefaultChannel(), 1, null, 0, 0, mNotiGroupGSortB, user,
- null, System.currentTimeMillis()));
+ "package", "package", 1, null, 0, 0, mNotiGroupGSortB, user,
+ null, System.currentTimeMillis()), getDefaultChannel());
mNotiNoGroup = new Notification.Builder(getContext())
.setContentTitle("C")
.setWhen(1201)
.build();
mRecordNoGroup = new NotificationRecord(getContext(), new StatusBarNotification(
- "package", "package", getDefaultChannel(), 1, null, 0, 0, mNotiNoGroup, user,
- null, System.currentTimeMillis()));
+ "package", "package", 1, null, 0, 0, mNotiNoGroup, user,
+ null, System.currentTimeMillis()), getDefaultChannel());
mNotiNoGroup2 = new Notification.Builder(getContext())
.setContentTitle("D")
.setWhen(1202)
.build();
mRecordNoGroup2 = new NotificationRecord(getContext(), new StatusBarNotification(
- "package", "package", getDefaultChannel(), 1, null, 0, 0, mNotiNoGroup2, user,
- null, System.currentTimeMillis()));
+ "package", "package", 1, null, 0, 0, mNotiNoGroup2, user,
+ null, System.currentTimeMillis()), getDefaultChannel());
mNotiNoGroupSortA = new Notification.Builder(getContext())
.setContentTitle("E")
@@ -140,8 +140,8 @@
.setSortKey("A")
.build();
mRecordNoGroupSortA = new NotificationRecord(getContext(), new StatusBarNotification(
- "package", "package", getDefaultChannel(), 1, null, 0, 0, mNotiNoGroupSortA, user,
- null, System.currentTimeMillis()));
+ "package", "package", 1, null, 0, 0, mNotiNoGroupSortA, user,
+ null, System.currentTimeMillis()), getDefaultChannel());
final ApplicationInfo legacy = new ApplicationInfo();
legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
@@ -254,8 +254,12 @@
mHelper.createNotificationChannel(pkg, uid, channel1, true);
mHelper.createNotificationChannel(pkg, uid, channel2, false);
+ mHelper.setShowBadge(pkg, uid, true);
+ mHelper.setShowBadge(pkg2, uid2, false);
+
ByteArrayOutputStream baos = writeXmlAndPurge(pkg, uid, channel1.getId(), channel2.getId(),
NotificationChannel.DEFAULT_CHANNEL_ID);
+ mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{pkg}, new int[]{uid});
XmlPullParser parser = Xml.newPullParser();
parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
@@ -263,6 +267,8 @@
parser.nextTag();
mHelper.readXml(parser, false);
+ assertFalse(mHelper.canShowBadge(pkg2, uid2));
+ assertTrue(mHelper.canShowBadge(pkg, uid));
assertEquals(channel1, mHelper.getNotificationChannel(pkg, uid, channel1.getId(), false));
compareChannels(channel2,
mHelper.getNotificationChannel(pkg, uid, channel2.getId(), false));
@@ -758,4 +764,11 @@
mHelper.onPackagesChanged(false, UserHandle.USER_SYSTEM, new String[]{pkg}, new int[]{uid});
assertEquals(2, mHelper.getNotificationChannels(pkg, uid, false).getList().size());
}
+
+ @Test
+ public void testRecordDefaults() throws Exception {
+ assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(pkg, uid));
+ assertEquals(true, mHelper.canShowBadge(pkg, uid));
+ assertEquals(1, mHelper.getNotificationChannels(pkg, uid, false).getList().size());
+ }
}
diff --git a/services/tests/notification/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/notification/src/com/android/server/notification/SnoozeHelperTest.java
index 460fcdf..b7931d4 100644
--- a/services/tests/notification/src/com/android/server/notification/SnoozeHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/SnoozeHelperTest.java
@@ -199,8 +199,8 @@
.setWhen(1205)
.build();
return new NotificationRecord(getContext(), new StatusBarNotification(
- pkg, pkg, getDefaultChannel(), id, tag, 0, 0, n, user, null,
- System.currentTimeMillis()));
+ pkg, pkg, id, tag, 0, 0, n, user, null,
+ System.currentTimeMillis()), getDefaultChannel());
}
private NotificationChannel getDefaultChannel() {
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index a3f7c18..b28627b 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -4791,6 +4791,20 @@
}
}
+ /*
+ * @return true, if the device is currently on a technology (e.g. UMTS or LTE) which can support
+ * voice and data simultaneously. This can change based on location or network condition.
+ */
+ public boolean isConcurrentVoiceAndDataAllowed() {
+ try {
+ ITelephony telephony = getITelephony();
+ return (telephony == null ? false : telephony.isConcurrentVoiceAndDataAllowed(mSubId));
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#isConcurrentVoiceAndDataAllowed", e);
+ }
+ return false;
+ }
+
/** @hide */
@SystemApi
public boolean handlePinMmi(String dialString) {
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index c0d6768ae..9a9a092 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -466,6 +466,12 @@
*/
int getVoiceMessageCountForSubscriber(int subId);
+ /**
+ * Returns true if current state supports both voice and data
+ * simultaneously. This can change based on location or network condition.
+ */
+ boolean isConcurrentVoiceAndDataAllowed(int subId);
+
oneway void setVisualVoicemailEnabled(String callingPackage,
in PhoneAccountHandle accountHandle, boolean enabled);