Messaging: Add scheduled send, location share, app lock, and settings polish
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+5
-3
@@ -87,11 +87,13 @@ android_app {
|
|||||||
"1.0.001",
|
"1.0.001",
|
||||||
"--version-code",
|
"--version-code",
|
||||||
"10001040",
|
"10001040",
|
||||||
|
"--rename-manifest-package",
|
||||||
|
"com.filabcorp.msgq",
|
||||||
],
|
],
|
||||||
required: [
|
required: [
|
||||||
"libframesequence",
|
"libframesequence",
|
||||||
"libgiftranscode",
|
"libgiftranscode",
|
||||||
"com.bnkode.notie.allowlist",
|
"com.filabcorp.msgq.allowlist",
|
||||||
],
|
],
|
||||||
optimize: {
|
optimize: {
|
||||||
obfuscate: true,
|
obfuscate: true,
|
||||||
@@ -105,9 +107,9 @@ android_app {
|
|||||||
}
|
}
|
||||||
|
|
||||||
prebuilt_etc {
|
prebuilt_etc {
|
||||||
name: "com.bnkode.notie.allowlist",
|
name: "com.filabcorp.msgq.allowlist",
|
||||||
product_specific: true,
|
product_specific: true,
|
||||||
sub_dir: "sysconfig",
|
sub_dir: "sysconfig",
|
||||||
src: "com.bnkode.notie.allowlist.xml",
|
src: "com.filabcorp.msgq.allowlist.xml",
|
||||||
filename_from_src: true,
|
filename_from_src: true,
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-3
@@ -13,13 +13,13 @@ android {
|
|||||||
compileSdk 35
|
compileSdk 35
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId 'com.bnkode.notie'
|
applicationId 'com.filabcorp.msgq'
|
||||||
minSdk 33
|
minSdk 33
|
||||||
targetSdk 34
|
targetSdk 34
|
||||||
versionCode 10001040
|
versionCode 10001040
|
||||||
versionName '1.0.001'
|
versionName '1.0.001'
|
||||||
vectorDrawables.useSupportLibrary true
|
vectorDrawables.useSupportLibrary true
|
||||||
resourceConfigurations += ['en']
|
resourceConfigurations += ['en', 'ko']
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
@@ -78,6 +78,7 @@ dependencies {
|
|||||||
implementation 'androidx.core:core:1.15.0'
|
implementation 'androidx.core:core:1.15.0'
|
||||||
implementation 'androidx.exifinterface:exifinterface:1.3.7'
|
implementation 'androidx.exifinterface:exifinterface:1.3.7'
|
||||||
implementation 'androidx.fragment:fragment:1.8.5'
|
implementation 'androidx.fragment:fragment:1.8.5'
|
||||||
|
implementation 'androidx.lifecycle:lifecycle-process:2.8.7'
|
||||||
implementation 'androidx.media:media:1.7.0'
|
implementation 'androidx.media:media:1.7.0'
|
||||||
implementation 'androidx.palette:palette:1.0.0'
|
implementation 'androidx.palette:palette:1.0.0'
|
||||||
implementation 'androidx.preference:preference:1.2.1'
|
implementation 'androidx.preference:preference:1.2.1'
|
||||||
@@ -104,5 +105,5 @@ tasks.register('launchMessaging', Exec) {
|
|||||||
description = "Launch Messaging on ${messagingAvdSerial}"
|
description = "Launch Messaging on ${messagingAvdSerial}"
|
||||||
dependsOn 'installMessaging'
|
dependsOn 'installMessaging'
|
||||||
commandLine adbExecutable.absolutePath, '-s', messagingAvdSerial, 'shell', 'am', 'start',
|
commandLine adbExecutable.absolutePath, '-s', messagingAvdSerial, 'shell', 'am', 'start',
|
||||||
'-n', 'com.bnkode.notie/com.android.messaging.ui.conversationlist.ConversationListActivity'
|
'-n', 'com.filabcorp.msgq/com.android.messaging.ui.conversationlist.ConversationListActivity'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,10 @@
|
|||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||||
|
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
|
||||||
|
|
||||||
<!-- Optional features -->
|
<!-- Optional features -->
|
||||||
<uses-feature
|
<uses-feature
|
||||||
@@ -63,9 +67,15 @@
|
|||||||
<uses-feature
|
<uses-feature
|
||||||
android:name="android.hardware.screen.portrait"
|
android:name="android.hardware.screen.portrait"
|
||||||
android:required="false" />
|
android:required="false" />
|
||||||
<uses-feature
|
<uses-feature
|
||||||
android:name="android.hardware.telephony"
|
android:name="android.hardware.telephony"
|
||||||
android:required="false" />
|
android:required="false" />
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.hardware.location"
|
||||||
|
android:required="false" />
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.hardware.location.gps"
|
||||||
|
android:required="false" />
|
||||||
|
|
||||||
<queries>
|
<queries>
|
||||||
<intent>
|
<intent>
|
||||||
@@ -82,6 +92,7 @@
|
|||||||
android:appCategory="social"
|
android:appCategory="social"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
android:localeConfig="@xml/locales_config"
|
||||||
android:requestLegacyExternalStorage="true"
|
android:requestLegacyExternalStorage="true"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/BugleTheme">
|
android:theme="@style/BugleTheme">
|
||||||
@@ -206,6 +217,32 @@
|
|||||||
android:theme="@style/Home.BottomSheet"
|
android:theme="@style/Home.BottomSheet"
|
||||||
android:windowSoftInputMode="adjustResize|stateHidden" />
|
android:windowSoftInputMode="adjustResize|stateHidden" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".ui.home.HomeLocationActivity"
|
||||||
|
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||||
|
android:label="@string/home_location_title"
|
||||||
|
android:parentActivityName="com.android.messaging.ui.conversationlist.ConversationListActivity"
|
||||||
|
android:screenOrientation="user"
|
||||||
|
android:theme="@style/Home.SubScreen" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".ui.home.HomeSecuritySettingsActivity"
|
||||||
|
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||||
|
android:label="@string/home_security_title"
|
||||||
|
android:parentActivityName="com.android.messaging.ui.appsettings.ApplicationSettingsActivity"
|
||||||
|
android:screenOrientation="user"
|
||||||
|
android:theme="@style/Home.SubScreen" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".ui.home.HomeAppLockActivity"
|
||||||
|
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||||
|
android:excludeFromRecents="true"
|
||||||
|
android:label="@string/home_settings_app_lock"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:screenOrientation="user"
|
||||||
|
android:theme="@style/Home.SubScreen"
|
||||||
|
android:windowSoftInputMode="adjustResize" />
|
||||||
|
|
||||||
<!-- Full-screen photo viewer -->
|
<!-- Full-screen photo viewer -->
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.photoviewer.BuglePhotoViewActivity"
|
android:name=".ui.photoviewer.BuglePhotoViewActivity"
|
||||||
@@ -320,7 +357,8 @@
|
|||||||
android:configChanges="orientation|screenSize|keyboardHidden"
|
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:screenOrientation="user"
|
android:screenOrientation="user"
|
||||||
android:theme="@style/Invisible">
|
android:theme="@style/Home.TransparentSheetHost"
|
||||||
|
android:windowSoftInputMode="adjustResize">
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<!-- VCard details -->
|
<!-- VCard details -->
|
||||||
@@ -343,20 +381,20 @@
|
|||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name=".datamodel.MessagingContentProvider"
|
android:name=".datamodel.MessagingContentProvider"
|
||||||
android:authorities="com.bnkode.notie.datamodel.MessagingContentProvider"
|
android:authorities="com.filabcorp.msgq.datamodel.MessagingContentProvider"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:label="@string/app_name">
|
android:label="@string/app_name">
|
||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name=".datamodel.MmsFileProvider"
|
android:name=".datamodel.MmsFileProvider"
|
||||||
android:authorities="com.bnkode.notie.datamodel.MmsFileProvider"
|
android:authorities="com.filabcorp.msgq.datamodel.MmsFileProvider"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:grantUriPermissions="true" />
|
android:grantUriPermissions="true" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name=".datamodel.MediaScratchFileProvider"
|
android:name=".datamodel.MediaScratchFileProvider"
|
||||||
android:authorities="com.bnkode.notie.datamodel.MediaScratchFileProvider"
|
android:authorities="com.filabcorp.msgq.datamodel.MediaScratchFileProvider"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:grantUriPermissions="true" />
|
android:grantUriPermissions="true" />
|
||||||
|
|
||||||
@@ -412,18 +450,18 @@
|
|||||||
android:name=".receiver.SendStatusReceiver"
|
android:name=".receiver.SendStatusReceiver"
|
||||||
android:exported="false">
|
android:exported="false">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="com.bnkode.notie.receiver.SendStatusReceiver.MESSAGE_SENT" />
|
<action android:name="com.filabcorp.msgq.receiver.SendStatusReceiver.MESSAGE_SENT" />
|
||||||
<data android:scheme="content" />
|
<data android:scheme="content" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="com.bnkode.notie.receiver.SendStatusReceiver.MESSAGE_DELIVERED" />
|
<action android:name="com.filabcorp.msgq.receiver.SendStatusReceiver.MESSAGE_DELIVERED" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="com.bnkode.notie.receiver.SendStatusReceiver.MMS_SENT" />
|
<action android:name="com.filabcorp.msgq.receiver.SendStatusReceiver.MMS_SENT" />
|
||||||
<data android:scheme="content" />
|
<data android:scheme="content" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="com.bnkode.notie.receiver.SendStatusReceiver.MMS_DOWNLOADED" />
|
<action android:name="com.filabcorp.msgq.receiver.SendStatusReceiver.MMS_DOWNLOADED" />
|
||||||
<data android:scheme="content" />
|
<data android:scheme="content" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
@@ -462,6 +500,14 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name=".receiver.ScheduledMessageReceiver"
|
||||||
|
android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.filabcorp.msgq.SEND_SCHEDULED_MESSAGE" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
<!-- Broadcast receiver that will be notified to reset notifications -->
|
<!-- Broadcast receiver that will be notified to reset notifications -->
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".receiver.NotificationReceiver"
|
android:name=".receiver.NotificationReceiver"
|
||||||
@@ -473,7 +519,7 @@
|
|||||||
android:name=".datamodel.action.ActionServiceImpl$PendingActionReceiver"
|
android:name=".datamodel.action.ActionServiceImpl$PendingActionReceiver"
|
||||||
android:exported="false">
|
android:exported="false">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="com.bnkode.notie.datamodel.PENDING_ACTION" />
|
<action android:name="com.filabcorp.msgq.datamodel.PENDING_ACTION" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/>
|
||||||
|
<style>
|
||||||
|
html, body, #map { height: 100%; margin: 0; padding: 0; background: #f4f1fb; }
|
||||||
|
.leaflet-control-attribution { font-size: 10px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="map"></div>
|
||||||
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||||
|
<script>
|
||||||
|
var map = L.map('map', { zoomControl: false }).setView([37.5665, 126.9780], 16);
|
||||||
|
L.control.zoom({ position: 'bottomright' }).addTo(map);
|
||||||
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||||
|
maxZoom: 19,
|
||||||
|
attribution: '© OpenStreetMap'
|
||||||
|
}).addTo(map);
|
||||||
|
function notify() {
|
||||||
|
var c = map.getCenter();
|
||||||
|
if (window.LocationBridge) {
|
||||||
|
LocationBridge.onMapMoved(c.lat, c.lng);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
map.on('moveend', notify);
|
||||||
|
function setCenter(lat, lng) {
|
||||||
|
map.setView([lat, lng], Math.max(map.getZoom(), 16));
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -36,6 +36,7 @@ import com.android.messaging.sms.BugleApnSettingsLoader;
|
|||||||
import com.android.messaging.sms.BugleUserAgentInfoLoader;
|
import com.android.messaging.sms.BugleUserAgentInfoLoader;
|
||||||
import com.android.messaging.sms.MmsConfig;
|
import com.android.messaging.sms.MmsConfig;
|
||||||
import com.android.messaging.ui.ConversationDrawables;
|
import com.android.messaging.ui.ConversationDrawables;
|
||||||
|
import com.android.messaging.ui.home.HomeAppLock;
|
||||||
import com.android.messaging.ui.home.HomeToast;
|
import com.android.messaging.ui.home.HomeToast;
|
||||||
import com.android.messaging.util.BuglePrefsKeys;
|
import com.android.messaging.util.BuglePrefsKeys;
|
||||||
import com.android.messaging.util.LogUtil;
|
import com.android.messaging.util.LogUtil;
|
||||||
@@ -58,6 +59,7 @@ public class BugleApplication extends Application implements UncaughtExceptionHa
|
|||||||
super.onCreate();
|
super.onCreate();
|
||||||
|
|
||||||
FactoryImpl.register(getApplicationContext(), this);
|
FactoryImpl.register(getApplicationContext(), this);
|
||||||
|
HomeAppLock.install(this);
|
||||||
HomeToast.install(this);
|
HomeToast.install(this);
|
||||||
|
|
||||||
sSystemUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
|
sSystemUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
|
||||||
|
|||||||
@@ -722,7 +722,9 @@ public class BugleNotifications {
|
|||||||
setWearableGroupOptions(notifBuilder, notificationState);
|
setWearableGroupOptions(notifBuilder, notificationState);
|
||||||
|
|
||||||
if (notificationState instanceof MultiMessageNotificationState) {
|
if (notificationState instanceof MultiMessageNotificationState) {
|
||||||
if (attachmentBitmap != null) {
|
if (attachmentBitmap != null
|
||||||
|
&& notificationState.mNotificationStyle
|
||||||
|
instanceof NotificationCompat.BigPictureStyle) {
|
||||||
// When we've got a picture attachment, we do some switcheroo trickery. When
|
// When we've got a picture attachment, we do some switcheroo trickery. When
|
||||||
// the notification is expanded, we show the picture as a bigPicture. The small
|
// the notification is expanded, we show the picture as a bigPicture. The small
|
||||||
// icon shows the sender's avatar. When that same notification is collapsed, the
|
// icon shows the sender's avatar. When that same notification is collapsed, the
|
||||||
@@ -787,28 +789,34 @@ public class BugleNotifications {
|
|||||||
final int requestCode = multiMessageNotificationState.getReplyIntentRequestCode();
|
final int requestCode = multiMessageNotificationState.getReplyIntentRequestCode();
|
||||||
final PendingIntent replyPendingIntent = UIIntents.get()
|
final PendingIntent replyPendingIntent = UIIntents.get()
|
||||||
.getPendingIntentForSendingMessageToConversation(context,
|
.getPendingIntentForSendingMessageToConversation(context,
|
||||||
conversationId, selfId, requiresMms, requestCode);
|
conversationId, selfId, requiresMms,
|
||||||
|
multiMessageNotificationState.mTitle, requestCode);
|
||||||
|
|
||||||
final int replyLabelRes = requiresMms ? R.string.notification_reply_via_mms :
|
// Phone: open the Home bottom-sheet composer. Wear keeps RemoteInput.
|
||||||
R.string.notification_reply_via_sms;
|
final NotificationCompat.Action replyAction =
|
||||||
|
|
||||||
final NotificationCompat.Action.Builder actionBuilder =
|
|
||||||
new NotificationCompat.Action.Builder(R.drawable.ic_wear_reply,
|
new NotificationCompat.Action.Builder(R.drawable.ic_wear_reply,
|
||||||
context.getString(replyLabelRes), replyPendingIntent);
|
context.getString(R.string.home_notification_reply), replyPendingIntent)
|
||||||
final RemoteInput.Builder remoteInputBuilder = new RemoteInput.Builder(Intent.EXTRA_TEXT);
|
.setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_REPLY)
|
||||||
remoteInputBuilder.setLabel(context.getString(R.string.notification_reply_prompt));
|
.setShowsUserInterface(true)
|
||||||
actionBuilder.addRemoteInput(remoteInputBuilder.build());
|
.build();
|
||||||
notifBuilder.addAction(actionBuilder.build());
|
notifBuilder.addAction(replyAction);
|
||||||
|
|
||||||
// Support the action on a wearable device
|
// Support the action on a wearable device
|
||||||
final NotificationCompat.Action.Builder wearActionBuilder =
|
|
||||||
new NotificationCompat.Action.Builder(R.drawable.ic_wear_reply,
|
|
||||||
context.getString(replyLabelRes), replyPendingIntent);
|
|
||||||
final String[] choices = context.getResources().getStringArray(
|
final String[] choices = context.getResources().getStringArray(
|
||||||
R.array.notification_reply_choices);
|
R.array.notification_reply_choices);
|
||||||
remoteInputBuilder.setChoices(choices);
|
final RemoteInput wearRemoteInput = new RemoteInput.Builder(Intent.EXTRA_TEXT)
|
||||||
wearActionBuilder.addRemoteInput(remoteInputBuilder.build());
|
.setLabel(context.getString(R.string.home_notification_reply_hint))
|
||||||
wearableExtender.addAction(wearActionBuilder.build());
|
.setAllowFreeFormInput(true)
|
||||||
|
.setChoices(choices)
|
||||||
|
.build();
|
||||||
|
final int wearLabelRes = requiresMms ? R.string.notification_reply_via_mms :
|
||||||
|
R.string.notification_reply_via_sms;
|
||||||
|
wearableExtender.addAction(new NotificationCompat.Action.Builder(R.drawable.ic_wear_reply,
|
||||||
|
context.getString(wearLabelRes), replyPendingIntent)
|
||||||
|
.addRemoteInput(wearRemoteInput)
|
||||||
|
.setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_REPLY)
|
||||||
|
.setShowsUserInterface(false)
|
||||||
|
.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addDownloadMmsAction(final NotificationCompat.Builder notifBuilder,
|
private static void addDownloadMmsAction(final NotificationCompat.Builder notifBuilder,
|
||||||
|
|||||||
@@ -120,6 +120,8 @@ public class FrequentContactsCursorBuilder {
|
|||||||
mAllContactsCursor.getInt(ContactUtil.INDEX_PHONE_EMAIL_TYPE);
|
mAllContactsCursor.getInt(ContactUtil.INDEX_PHONE_EMAIL_TYPE);
|
||||||
row[ContactUtil.INDEX_PHONE_EMAIL_LABEL] =
|
row[ContactUtil.INDEX_PHONE_EMAIL_LABEL] =
|
||||||
mAllContactsCursor.getString(ContactUtil.INDEX_PHONE_EMAIL_LABEL);
|
mAllContactsCursor.getString(ContactUtil.INDEX_PHONE_EMAIL_LABEL);
|
||||||
|
row[ContactUtil.INDEX_SORT_KEY] =
|
||||||
|
mAllContactsCursor.getString(ContactUtil.INDEX_SORT_KEY);
|
||||||
|
|
||||||
boolean numberAlreadyAdded = false;
|
boolean numberAlreadyAdded = false;
|
||||||
for (Object[] oldRow : rows) {
|
for (Object[] oldRow : rows) {
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ public class MediaScratchFileProvider extends FileProvider {
|
|||||||
private static final SimpleArrayMap<Uri, String> sUriToDisplayNameMap = new SimpleArrayMap<>();
|
private static final SimpleArrayMap<Uri, String> sUriToDisplayNameMap = new SimpleArrayMap<>();
|
||||||
|
|
||||||
public static final String AUTHORITY =
|
public static final String AUTHORITY =
|
||||||
"com.bnkode.notie.datamodel.MediaScratchFileProvider";
|
"com.filabcorp.msgq.datamodel.MediaScratchFileProvider";
|
||||||
private static final String MEDIA_SCRATCH_SPACE_DIR = "mediascratchspace";
|
private static final String MEDIA_SCRATCH_SPACE_DIR = "mediascratchspace";
|
||||||
|
|
||||||
public static boolean isMediaScratchSpaceUri(final Uri uri) {
|
public static boolean isMediaScratchSpaceUri(final Uri uri) {
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import androidx.core.app.NotificationCompat;
|
|||||||
import androidx.core.app.NotificationCompat.Builder;
|
import androidx.core.app.NotificationCompat.Builder;
|
||||||
import androidx.core.app.NotificationCompat.WearableExtender;
|
import androidx.core.app.NotificationCompat.WearableExtender;
|
||||||
import androidx.core.app.NotificationManagerCompat;
|
import androidx.core.app.NotificationManagerCompat;
|
||||||
|
import androidx.core.app.Person;
|
||||||
|
|
||||||
import com.android.messaging.Factory;
|
import com.android.messaging.Factory;
|
||||||
import com.android.messaging.R;
|
import com.android.messaging.R;
|
||||||
@@ -494,85 +495,62 @@ public abstract class MessageNotificationState extends NotificationState {
|
|||||||
@Override
|
@Override
|
||||||
protected NotificationCompat.Style build(final Builder builder) {
|
protected NotificationCompat.Style build(final Builder builder) {
|
||||||
builder.setContentTitle(mTitle)
|
builder.setContentTitle(mTitle)
|
||||||
.setTicker(getTicker());
|
.setTicker(getTicker())
|
||||||
|
.setContentText(mContent);
|
||||||
|
|
||||||
NotificationCompat.Style notifStyle;
|
final Context context = Factory.get().getApplicationContext();
|
||||||
final ConversationLineInfo convInfo = mConvList.mConvInfos.get(0);
|
final ConversationLineInfo convInfo = mConvList.mConvInfos.get(0);
|
||||||
final List<NotificationLineInfo> lineInfos = convInfo.mLineInfos;
|
final List<NotificationLineInfo> lineInfos = convInfo.mLineInfos;
|
||||||
final int messageCount = lineInfos.size();
|
final Person user = new Person.Builder()
|
||||||
// At this point, all the messages come from the same conversation. We need to load
|
.setName(context.getString(R.string.home_notification_you))
|
||||||
// the sender's avatar and then finish building the notification on a callback.
|
.build();
|
||||||
|
final NotificationCompat.MessagingStyle style =
|
||||||
builder.setContentText(mContent); // for collapsed state
|
new NotificationCompat.MessagingStyle(user);
|
||||||
|
if (convInfo.mIsGroup) {
|
||||||
if (messageCount == 1) {
|
style.setGroupConversation(true);
|
||||||
final boolean shouldShowImage = ContentType.isImageType(mAttachmentType)
|
if (!TextUtils.isEmpty(convInfo.mGroupConversationName)) {
|
||||||
|| (ContentType.isVideoType(mAttachmentType));
|
style.setConversationTitle(convInfo.mGroupConversationName);
|
||||||
if (mAttachmentUri != null && shouldShowImage) {
|
|
||||||
// Show "Picture" as the content
|
|
||||||
final MessageLineInfo messageLineInfo = (MessageLineInfo) lineInfos.get(0);
|
|
||||||
String authorFirstName = messageLineInfo.mAuthorFirstName;
|
|
||||||
|
|
||||||
// For the collapsed state, just show "picture" unless this is a
|
|
||||||
// group conversation. If it's a group, show the sender name and
|
|
||||||
// "picture".
|
|
||||||
final CharSequence tickerTag =
|
|
||||||
BugleNotifications.formatAttachmentTag(authorFirstName,
|
|
||||||
mAttachmentType);
|
|
||||||
// For 1:1 notifications don't show first name in the notification, but
|
|
||||||
// do show it in the ticker text
|
|
||||||
CharSequence pictureTag = tickerTag;
|
|
||||||
if (!convInfo.mIsGroup) {
|
|
||||||
authorFirstName = null;
|
|
||||||
pictureTag = BugleNotifications.formatAttachmentTag(authorFirstName,
|
|
||||||
mAttachmentType);
|
|
||||||
}
|
|
||||||
builder.setContentText(pictureTag);
|
|
||||||
builder.setTicker(tickerTag);
|
|
||||||
|
|
||||||
notifStyle = new NotificationCompat.BigPictureStyle(builder)
|
|
||||||
.setSummaryText(BugleNotifications.formatInboxMessage(
|
|
||||||
authorFirstName,
|
|
||||||
null, null,
|
|
||||||
null)); // expanded state, just show sender
|
|
||||||
} else {
|
|
||||||
notifStyle = new NotificationCompat.BigTextStyle(builder)
|
|
||||||
.bigText(mContent);
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// We've got multiple messages for the same sender.
|
|
||||||
// Starting with the oldest new message, display the full text of each message.
|
|
||||||
// Begin a line for each subsequent message.
|
|
||||||
final SpannableStringBuilder buf = new SpannableStringBuilder();
|
|
||||||
|
|
||||||
for (int i = lineInfos.size() - 1; i >= 0; --i) {
|
|
||||||
final NotificationLineInfo info = lineInfos.get(i);
|
|
||||||
final MessageLineInfo messageLineInfo = (MessageLineInfo) info;
|
|
||||||
mAttachmentUri = messageLineInfo.mAttachmentUri;
|
|
||||||
mAttachmentType = messageLineInfo.mAttachmentType;
|
|
||||||
CharSequence text = messageLineInfo.mText;
|
|
||||||
if (!TextUtils.isEmpty(text) || mAttachmentUri != null) {
|
|
||||||
if (convInfo.mIsGroup) {
|
|
||||||
// append the bold name to the front of the message
|
|
||||||
text = BugleNotifications.buildSpaceSeparatedMessage(
|
|
||||||
messageLineInfo.mAuthorFullName, text, mAttachmentUri,
|
|
||||||
mAttachmentType);
|
|
||||||
} else {
|
|
||||||
text = BugleNotifications.buildSpaceSeparatedMessage(
|
|
||||||
null, text, mAttachmentUri, mAttachmentType);
|
|
||||||
}
|
|
||||||
buf.append(text);
|
|
||||||
if (i > 0) {
|
|
||||||
buf.append('\n');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show a single notification -- big style with the text of all the messages
|
|
||||||
notifStyle = new NotificationCompat.BigTextStyle(builder).bigText(buf);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MessagingStyle wants chronological order (oldest first). Latest is at index 0.
|
||||||
|
for (int i = lineInfos.size() - 1; i >= 0; --i) {
|
||||||
|
final NotificationLineInfo info = lineInfos.get(i);
|
||||||
|
if (!(info instanceof MessageLineInfo)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final MessageLineInfo messageLineInfo = (MessageLineInfo) info;
|
||||||
|
CharSequence text = messageLineInfo.mText;
|
||||||
|
if (TextUtils.isEmpty(text) && messageLineInfo.mAttachmentUri != null) {
|
||||||
|
text = BugleNotifications.formatAttachmentTag(
|
||||||
|
convInfo.mIsGroup ? messageLineInfo.mAuthorFirstName : null,
|
||||||
|
messageLineInfo.mAttachmentType);
|
||||||
|
}
|
||||||
|
if (TextUtils.isEmpty(text)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final Person.Builder personBuilder = new Person.Builder()
|
||||||
|
.setName(!TextUtils.isEmpty(messageLineInfo.mAuthorFullName)
|
||||||
|
? messageLineInfo.mAuthorFullName : mTitle);
|
||||||
|
if (convInfo.mContactUri != null) {
|
||||||
|
personBuilder.setUri(convInfo.mContactUri.toString());
|
||||||
|
}
|
||||||
|
final long timestamp = Math.max(0, convInfo.mReceivedTimestamp - i);
|
||||||
|
final NotificationCompat.MessagingStyle.Message message =
|
||||||
|
new NotificationCompat.MessagingStyle.Message(
|
||||||
|
text, timestamp, personBuilder.build());
|
||||||
|
if (messageLineInfo.mAttachmentUri != null
|
||||||
|
&& !TextUtils.isEmpty(messageLineInfo.mAttachmentType)
|
||||||
|
&& (ContentType.isImageType(messageLineInfo.mAttachmentType)
|
||||||
|
|| ContentType.isVideoType(messageLineInfo.mAttachmentType))) {
|
||||||
|
message.setData(messageLineInfo.mAttachmentType,
|
||||||
|
messageLineInfo.mAttachmentUri);
|
||||||
|
}
|
||||||
|
style.addMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
builder.setWhen(convInfo.mReceivedTimestamp);
|
builder.setWhen(convInfo.mReceivedTimestamp);
|
||||||
return notifStyle;
|
return style;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ public class MessagingContentProvider extends ContentProvider {
|
|||||||
private static final String TAG = LogUtil.BUGLE_TAG;
|
private static final String TAG = LogUtil.BUGLE_TAG;
|
||||||
|
|
||||||
public static final String AUTHORITY =
|
public static final String AUTHORITY =
|
||||||
"com.bnkode.notie.datamodel.MessagingContentProvider";
|
"com.filabcorp.msgq.datamodel.MessagingContentProvider";
|
||||||
private static final String CONTENT_AUTHORITY = "content://" + AUTHORITY + '/';
|
private static final String CONTENT_AUTHORITY = "content://" + AUTHORITY + '/';
|
||||||
|
|
||||||
// Conversations query
|
// Conversations query
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import java.io.IOException;
|
|||||||
public class MmsFileProvider extends FileProvider {
|
public class MmsFileProvider extends FileProvider {
|
||||||
private static final String TAG = LogUtil.BUGLE_TAG;
|
private static final String TAG = LogUtil.BUGLE_TAG;
|
||||||
|
|
||||||
static final String AUTHORITY = "com.bnkode.notie.datamodel.MmsFileProvider";
|
static final String AUTHORITY = "com.filabcorp.msgq.datamodel.MmsFileProvider";
|
||||||
private static final String RAW_MMS_DIR = "rawmms";
|
private static final String RAW_MMS_DIR = "rawmms";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,236 @@
|
|||||||
|
package com.android.messaging.datamodel;
|
||||||
|
|
||||||
|
import android.app.AlarmManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Base64;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.WorkerThread;
|
||||||
|
|
||||||
|
import com.android.messaging.Factory;
|
||||||
|
import com.android.messaging.datamodel.action.InsertNewMessageAction;
|
||||||
|
import com.android.messaging.datamodel.data.MessageData;
|
||||||
|
import com.android.messaging.receiver.ScheduledMessageReceiver;
|
||||||
|
import com.android.messaging.util.LogUtil;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/** Persists compose-time scheduled sends and arms AlarmManager to deliver them. */
|
||||||
|
public final class ScheduledMessageStore {
|
||||||
|
private static final String PREFS = "home_scheduled_messages";
|
||||||
|
private static final String KEY_ENTRIES = "entries";
|
||||||
|
private static final String JSON_ID = "id";
|
||||||
|
private static final String JSON_SEND_AT = "sendAt";
|
||||||
|
private static final String JSON_PAYLOAD = "payload";
|
||||||
|
|
||||||
|
private ScheduledMessageStore() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void schedule(@NonNull final Context context, @NonNull final MessageData message,
|
||||||
|
final long sendAt) {
|
||||||
|
final Entry entry = new Entry();
|
||||||
|
entry.id = UUID.randomUUID().toString();
|
||||||
|
entry.sendAt = sendAt;
|
||||||
|
entry.payload = encode(message);
|
||||||
|
if (TextUtils.isEmpty(entry.payload)) {
|
||||||
|
LogUtil.e(LogUtil.BUGLE_TAG, "Scheduled message encode failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final List<Entry> entries = loadLocked(context);
|
||||||
|
entries.add(entry);
|
||||||
|
saveLocked(context, entries);
|
||||||
|
setAlarm(context, entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
public static void sendNow(@NonNull final Context context, @Nullable final String id) {
|
||||||
|
if (TextUtils.isEmpty(id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Entry match = null;
|
||||||
|
synchronized (ScheduledMessageStore.class) {
|
||||||
|
final List<Entry> entries = loadLocked(context);
|
||||||
|
for (int i = 0; i < entries.size(); i++) {
|
||||||
|
if (id.equals(entries.get(i).id)) {
|
||||||
|
match = entries.remove(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (match != null) {
|
||||||
|
saveLocked(context, entries);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (match == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cancelAlarm(context, match.id);
|
||||||
|
final MessageData message = decode(match.payload);
|
||||||
|
if (message == null || !message.hasContent()) {
|
||||||
|
LogUtil.e(LogUtil.BUGLE_TAG, "Scheduled message payload missing for " + id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
InsertNewMessageAction.insertNewMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void rescheduleAll(@NonNull final Context context) {
|
||||||
|
final List<Entry> entries = loadLocked(context);
|
||||||
|
final long now = System.currentTimeMillis();
|
||||||
|
final List<Entry> due = new ArrayList<>();
|
||||||
|
synchronized (ScheduledMessageStore.class) {
|
||||||
|
final List<Entry> remaining = new ArrayList<>();
|
||||||
|
for (final Entry entry : entries) {
|
||||||
|
if (entry.sendAt <= now) {
|
||||||
|
due.add(entry);
|
||||||
|
} else {
|
||||||
|
remaining.add(entry);
|
||||||
|
setAlarm(context, entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (due.size() != entries.size()) {
|
||||||
|
saveLocked(context, remaining);
|
||||||
|
} else if (!due.isEmpty()) {
|
||||||
|
saveLocked(context, new ArrayList<>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (final Entry entry : due) {
|
||||||
|
final MessageData message = decode(entry.payload);
|
||||||
|
if (message != null && message.hasContent()) {
|
||||||
|
InsertNewMessageAction.insertNewMessage(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setAlarm(@NonNull final Context context, @NonNull final Entry entry) {
|
||||||
|
final AlarmManager alarms = context.getSystemService(AlarmManager.class);
|
||||||
|
if (alarms == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final PendingIntent pending = pendingIntent(context, entry.id, true);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !alarms.canScheduleExactAlarms()) {
|
||||||
|
alarms.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, entry.sendAt, pending);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
alarms.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, entry.sendAt, pending);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void cancelAlarm(@NonNull final Context context, @NonNull final String id) {
|
||||||
|
final AlarmManager alarms = context.getSystemService(AlarmManager.class);
|
||||||
|
if (alarms == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final PendingIntent pending = pendingIntent(context, id, false);
|
||||||
|
if (pending != null) {
|
||||||
|
alarms.cancel(pending);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PendingIntent pendingIntent(@NonNull final Context context,
|
||||||
|
@NonNull final String id, final boolean update) {
|
||||||
|
final Intent intent = new Intent(context, ScheduledMessageReceiver.class);
|
||||||
|
intent.setAction(ScheduledMessageReceiver.ACTION_SEND);
|
||||||
|
intent.setData(Uri.parse("msgq-schedule:" + id));
|
||||||
|
intent.putExtra(ScheduledMessageReceiver.EXTRA_ID, id);
|
||||||
|
final int flags = PendingIntent.FLAG_IMMUTABLE
|
||||||
|
| (update ? PendingIntent.FLAG_UPDATE_CURRENT : PendingIntent.FLAG_NO_CREATE);
|
||||||
|
return PendingIntent.getBroadcast(context.getApplicationContext(), id.hashCode(),
|
||||||
|
intent, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static synchronized List<Entry> loadLocked(@NonNull final Context context) {
|
||||||
|
final SharedPreferences prefs = prefs(context);
|
||||||
|
final String raw = prefs.getString(KEY_ENTRIES, "[]");
|
||||||
|
final List<Entry> entries = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
final JSONArray array = new JSONArray(raw);
|
||||||
|
for (int i = 0; i < array.length(); i++) {
|
||||||
|
final JSONObject object = array.getJSONObject(i);
|
||||||
|
final Entry entry = new Entry();
|
||||||
|
entry.id = object.optString(JSON_ID);
|
||||||
|
entry.sendAt = object.optLong(JSON_SEND_AT);
|
||||||
|
entry.payload = object.optString(JSON_PAYLOAD);
|
||||||
|
if (!TextUtils.isEmpty(entry.id) && !TextUtils.isEmpty(entry.payload)) {
|
||||||
|
entries.add(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (final Exception exception) {
|
||||||
|
LogUtil.e(LogUtil.BUGLE_TAG, "Failed to load scheduled messages", exception);
|
||||||
|
}
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static synchronized void saveLocked(@NonNull final Context context,
|
||||||
|
@NonNull final List<Entry> entries) {
|
||||||
|
final JSONArray array = new JSONArray();
|
||||||
|
try {
|
||||||
|
for (final Entry entry : entries) {
|
||||||
|
final JSONObject object = new JSONObject();
|
||||||
|
object.put(JSON_ID, entry.id);
|
||||||
|
object.put(JSON_SEND_AT, entry.sendAt);
|
||||||
|
object.put(JSON_PAYLOAD, entry.payload);
|
||||||
|
array.put(object);
|
||||||
|
}
|
||||||
|
prefs(context).edit().putString(KEY_ENTRIES, array.toString()).apply();
|
||||||
|
} catch (final Exception exception) {
|
||||||
|
LogUtil.e(LogUtil.BUGLE_TAG, "Failed to save scheduled messages", exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SharedPreferences prefs(@NonNull final Context context) {
|
||||||
|
final Context app = context.getApplicationContext() != null
|
||||||
|
? context.getApplicationContext()
|
||||||
|
: Factory.get().getApplicationContext();
|
||||||
|
return app.getSharedPreferences(PREFS, Context.MODE_PRIVATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static String encode(@NonNull final MessageData message) {
|
||||||
|
final Parcel parcel = Parcel.obtain();
|
||||||
|
try {
|
||||||
|
message.writeToParcel(parcel, 0);
|
||||||
|
return Base64.encodeToString(parcel.marshall(), Base64.NO_WRAP);
|
||||||
|
} catch (final Exception exception) {
|
||||||
|
LogUtil.e(LogUtil.BUGLE_TAG, "Failed to encode scheduled message", exception);
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
parcel.recycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static MessageData decode(@Nullable final String payload) {
|
||||||
|
if (TextUtils.isEmpty(payload)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final Parcel parcel = Parcel.obtain();
|
||||||
|
try {
|
||||||
|
final byte[] bytes = Base64.decode(payload, Base64.NO_WRAP);
|
||||||
|
parcel.unmarshall(bytes, 0, bytes.length);
|
||||||
|
parcel.setDataPosition(0);
|
||||||
|
return MessageData.CREATOR.createFromParcel(parcel);
|
||||||
|
} catch (final Exception exception) {
|
||||||
|
LogUtil.e(LogUtil.BUGLE_TAG, "Failed to decode scheduled message", exception);
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
parcel.recycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class Entry {
|
||||||
|
String id;
|
||||||
|
long sendAt;
|
||||||
|
String payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -137,7 +137,7 @@ public class ActionServiceImpl extends JobIntentService {
|
|||||||
* Broadcast receiver for alarms scheduled through ActionService.
|
* Broadcast receiver for alarms scheduled through ActionService.
|
||||||
*/
|
*/
|
||||||
public static class PendingActionReceiver extends BroadcastReceiver {
|
public static class PendingActionReceiver extends BroadcastReceiver {
|
||||||
static final String ACTION = "com.bnkode.notie.datamodel.PENDING_ACTION";
|
static final String ACTION = "com.filabcorp.msgq.datamodel.PENDING_ACTION";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allocate an intent with a specific opcode and alarm action.
|
* Allocate an intent with a specific opcode and alarm action.
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ public class ContactPickerData extends BindableData implements
|
|||||||
private ContactPickerDataListener mListener;
|
private ContactPickerDataListener mListener;
|
||||||
private final FrequentContactsCursorBuilder mFrequentContactsCursorBuilder;
|
private final FrequentContactsCursorBuilder mFrequentContactsCursorBuilder;
|
||||||
private String mSearchText;
|
private String mSearchText;
|
||||||
|
private ContactUtil.AccountFilter mAccountFilter;
|
||||||
|
|
||||||
public ContactPickerData(final Context context, final ContactPickerDataListener listener) {
|
public ContactPickerData(final Context context, final ContactPickerDataListener listener) {
|
||||||
mListener = listener;
|
mListener = listener;
|
||||||
@@ -82,7 +83,7 @@ public class ContactPickerData extends BindableData implements
|
|||||||
if (isBound(bindingId)) {
|
if (isBound(bindingId)) {
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case ALL_CONTACTS_LOADER:
|
case ALL_CONTACTS_LOADER:
|
||||||
return ContactUtil.getPhones(mContext)
|
return ContactUtil.getPhones(mContext, mAccountFilter)
|
||||||
.createBoundCursorLoader(bindingId);
|
.createBoundCursorLoader(bindingId);
|
||||||
case FREQUENT_CONTACTS_LOADER:
|
case FREQUENT_CONTACTS_LOADER:
|
||||||
return ContactUtil.getFrequentContacts(mContext)
|
return ContactUtil.getFrequentContacts(mContext)
|
||||||
@@ -92,7 +93,7 @@ public class ContactPickerData extends BindableData implements
|
|||||||
MessagingContentProvider.PARTICIPANTS_URI,
|
MessagingContentProvider.PARTICIPANTS_URI,
|
||||||
ParticipantData.ParticipantsQuery.PROJECTION, null, null, null);
|
ParticipantData.ParticipantsQuery.PROJECTION, null, null, null);
|
||||||
case FILTERED_CONTACTS_LOADER:
|
case FILTERED_CONTACTS_LOADER:
|
||||||
return ContactUtil.filterDestination(mContext, mSearchText)
|
return ContactUtil.filterDestination(mContext, mSearchText, mAccountFilter)
|
||||||
.createBoundCursorLoader(bindingId);
|
.createBoundCursorLoader(bindingId);
|
||||||
default:
|
default:
|
||||||
Assert.fail("Unknown loader id for contact picker!");
|
Assert.fail("Unknown loader id for contact picker!");
|
||||||
@@ -217,6 +218,29 @@ public class ContactPickerData extends BindableData implements
|
|||||||
mLoaderManager.restartLoader(FILTERED_CONTACTS_LOADER, args, this);
|
mLoaderManager.restartLoader(FILTERED_CONTACTS_LOADER, args, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reloads the list for one contacts account, or every account when {@code filter} is null.
|
||||||
|
*/
|
||||||
|
public void setAccountFilter(final BindingBase<ContactPickerData> binding,
|
||||||
|
final ContactUtil.AccountFilter filter) {
|
||||||
|
if (mLoaderManager == null) {
|
||||||
|
mAccountFilter = filter;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final boolean same = (filter == null && mAccountFilter == null)
|
||||||
|
|| (filter != null && filter.matches(mAccountFilter));
|
||||||
|
if (same) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mAccountFilter = filter;
|
||||||
|
final Bundle args = new Bundle();
|
||||||
|
args.putString(BINDING_ID, binding.getBindingId());
|
||||||
|
mLoaderManager.restartLoader(ALL_CONTACTS_LOADER, args, this);
|
||||||
|
if (mSearchText != null) {
|
||||||
|
mLoaderManager.restartLoader(FILTERED_CONTACTS_LOADER, args, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void unregisterListeners() {
|
protected void unregisterListeners() {
|
||||||
mListener = null;
|
mListener = null;
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import android.content.Intent;
|
|||||||
|
|
||||||
import com.android.messaging.BugleApplication;
|
import com.android.messaging.BugleApplication;
|
||||||
import com.android.messaging.Factory;
|
import com.android.messaging.Factory;
|
||||||
|
import com.android.messaging.datamodel.ScheduledMessageStore;
|
||||||
import com.android.messaging.datamodel.action.UpdateMessageNotificationAction;
|
import com.android.messaging.datamodel.action.UpdateMessageNotificationAction;
|
||||||
import com.android.messaging.util.BuglePrefsKeys;
|
import com.android.messaging.util.BuglePrefsKeys;
|
||||||
import com.android.messaging.util.LogUtil;
|
import com.android.messaging.util.LogUtil;
|
||||||
@@ -40,6 +41,7 @@ public class BootAndPackageReplacedReceiver extends BroadcastReceiver {
|
|||||||
UpdateMessageNotificationAction.updateMessageNotification();
|
UpdateMessageNotificationAction.updateMessageNotification();
|
||||||
|
|
||||||
BugleApplication.updateAppConfig(context);
|
BugleApplication.updateAppConfig(context);
|
||||||
|
ScheduledMessageStore.rescheduleAll(context);
|
||||||
} else {
|
} else {
|
||||||
LogUtil.i(LogUtil.BUGLE_TAG, "BootAndPackageReplacedReceiver got unexpected action: "
|
LogUtil.i(LogUtil.BUGLE_TAG, "BootAndPackageReplacedReceiver got unexpected action: "
|
||||||
+ intent.getAction());
|
+ intent.getAction());
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.android.messaging.receiver;
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import com.android.messaging.datamodel.ScheduledMessageStore;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
/** Fires a previously scheduled compose message when its AlarmManager time is reached. */
|
||||||
|
public class ScheduledMessageReceiver extends BroadcastReceiver {
|
||||||
|
public static final String ACTION_SEND = "com.filabcorp.msgq.SEND_SCHEDULED_MESSAGE";
|
||||||
|
public static final String EXTRA_ID = "scheduled_id";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(final Context context, final Intent intent) {
|
||||||
|
if (intent == null || !ACTION_SEND.equals(intent.getAction())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final String id = intent.getStringExtra(EXTRA_ID);
|
||||||
|
if (TextUtils.isEmpty(id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final PendingResult pending = goAsync();
|
||||||
|
Executors.newSingleThreadExecutor().execute(() -> {
|
||||||
|
try {
|
||||||
|
ScheduledMessageStore.sendNow(context.getApplicationContext(), id);
|
||||||
|
} finally {
|
||||||
|
pending.finish();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,13 +38,13 @@ import com.android.messaging.util.LogUtil;
|
|||||||
*/
|
*/
|
||||||
public class SendStatusReceiver extends BroadcastReceiver {
|
public class SendStatusReceiver extends BroadcastReceiver {
|
||||||
public static final String MESSAGE_SENT_ACTION =
|
public static final String MESSAGE_SENT_ACTION =
|
||||||
"com.bnkode.notie.receiver.SendStatusReceiver.MESSAGE_SENT";
|
"com.filabcorp.msgq.receiver.SendStatusReceiver.MESSAGE_SENT";
|
||||||
public static final String MESSAGE_DELIVERED_ACTION =
|
public static final String MESSAGE_DELIVERED_ACTION =
|
||||||
"com.bnkode.notie.receiver.SendStatusReceiver.MESSAGE_DELIVERED";
|
"com.filabcorp.msgq.receiver.SendStatusReceiver.MESSAGE_DELIVERED";
|
||||||
public static final String MMS_SENT_ACTION =
|
public static final String MMS_SENT_ACTION =
|
||||||
"com.bnkode.notie.receiver.SendStatusReceiver.MMS_SENT";
|
"com.filabcorp.msgq.receiver.SendStatusReceiver.MMS_SENT";
|
||||||
public static final String MMS_DOWNLOADED_ACTION =
|
public static final String MMS_DOWNLOADED_ACTION =
|
||||||
"com.bnkode.notie.receiver.SendStatusReceiver.MMS_DOWNLOADED";
|
"com.filabcorp.msgq.receiver.SendStatusReceiver.MMS_DOWNLOADED";
|
||||||
|
|
||||||
// Defined by platform, but no constant provided. See docs for SmsManager.sendTextMessage.
|
// Defined by platform, but no constant provided. See docs for SmsManager.sendTextMessage.
|
||||||
public static final String EXTRA_ERROR_CODE = "errorCode";
|
public static final String EXTRA_ERROR_CODE = "errorCode";
|
||||||
|
|||||||
@@ -18,9 +18,23 @@ package com.android.messaging.ui;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.telephony.TelephonyManager;
|
import android.telephony.TelephonyManager;
|
||||||
|
import android.text.Editable;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.text.TextWatcher;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.inputmethod.EditorInfo;
|
||||||
|
import android.view.inputmethod.InputMethodManager;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
|
||||||
|
import androidx.core.app.RemoteInput;
|
||||||
|
|
||||||
|
import com.android.messaging.R;
|
||||||
import com.android.messaging.datamodel.NoConfirmationSmsSendService;
|
import com.android.messaging.datamodel.NoConfirmationSmsSendService;
|
||||||
|
import com.android.messaging.ui.home.HomeBottomSheet;
|
||||||
import com.android.messaging.util.LogUtil;
|
import com.android.messaging.util.LogUtil;
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetDialog;
|
||||||
|
|
||||||
public class RemoteInputEntrypointActivity extends BaseBugleActivity {
|
public class RemoteInputEntrypointActivity extends BaseBugleActivity {
|
||||||
private static final String TAG = LogUtil.BUGLE_TAG;
|
private static final String TAG = LogUtil.BUGLE_TAG;
|
||||||
@@ -37,22 +51,111 @@ public class RemoteInputEntrypointActivity extends BaseBugleActivity {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform some action depending on the intent
|
|
||||||
String action = intent.getAction();
|
String action = intent.getAction();
|
||||||
if (Intent.ACTION_SENDTO.equals(action)) {
|
if (!Intent.ACTION_SENDTO.equals(action)) {
|
||||||
// Build and send the intent
|
|
||||||
final Intent sendIntent = new Intent(this, NoConfirmationSmsSendService.class);
|
|
||||||
sendIntent.setAction(TelephonyManager.ACTION_RESPOND_VIA_MESSAGE);
|
|
||||||
sendIntent.putExtras(intent);
|
|
||||||
// Wear apparently passes all of its extras via the clip data. Must pass it along.
|
|
||||||
sendIntent.setClipData(intent.getClipData());
|
|
||||||
startService(sendIntent);
|
|
||||||
setResult(RESULT_OK);
|
|
||||||
} else {
|
|
||||||
LogUtil.w(TAG, "Unrecognized intent action: " + action);
|
LogUtil.w(TAG, "Unrecognized intent action: " + action);
|
||||||
setResult(RESULT_CANCELED);
|
setResult(RESULT_CANCELED);
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
// This activity should never stick around after processing the intent
|
|
||||||
|
final CharSequence remoteText = readRemoteInputText(intent);
|
||||||
|
if (!TextUtils.isEmpty(remoteText)) {
|
||||||
|
sendReply(intent, remoteText.toString());
|
||||||
|
setResult(RESULT_OK);
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showReplySheet(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CharSequence readRemoteInputText(final Intent intent) {
|
||||||
|
final Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
|
||||||
|
if (remoteInput != null) {
|
||||||
|
return remoteInput.getCharSequence(Intent.EXTRA_TEXT);
|
||||||
|
}
|
||||||
|
return intent.getCharSequenceExtra(Intent.EXTRA_TEXT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showReplySheet(final Intent sourceIntent) {
|
||||||
|
final View custom = LayoutInflater.from(this)
|
||||||
|
.inflate(R.layout.home_notification_reply_sheet, null, false);
|
||||||
|
final EditText input = custom.findViewById(R.id.home_notification_reply_input);
|
||||||
|
final ImageButton send = custom.findViewById(R.id.home_notification_reply_send);
|
||||||
|
if (send != null) {
|
||||||
|
send.setEnabled(false);
|
||||||
|
send.setAlpha(0.4f);
|
||||||
|
}
|
||||||
|
if (input != null && send != null) {
|
||||||
|
input.addTextChangedListener(new TextWatcher() {
|
||||||
|
@Override
|
||||||
|
public void beforeTextChanged(final CharSequence s, final int start,
|
||||||
|
final int count, final int after) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(final CharSequence s, final int start, final int before,
|
||||||
|
final int count) {
|
||||||
|
final boolean hasText = !TextUtils.isEmpty(s.toString().trim());
|
||||||
|
send.setEnabled(hasText);
|
||||||
|
send.setAlpha(hasText ? 1f : 0.4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterTextChanged(final Editable s) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
input.setOnEditorActionListener((v, actionId, event) -> {
|
||||||
|
if (actionId == EditorInfo.IME_ACTION_SEND) {
|
||||||
|
submitReply(sourceIntent, input);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
send.setOnClickListener(v -> submitReply(sourceIntent, input));
|
||||||
|
}
|
||||||
|
|
||||||
|
CharSequence title = sourceIntent.getStringExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_NAME);
|
||||||
|
if (TextUtils.isEmpty(title)) {
|
||||||
|
title = getString(R.string.home_notification_reply);
|
||||||
|
}
|
||||||
|
final BottomSheetDialog dialog = HomeBottomSheet.builder(this)
|
||||||
|
.title(title)
|
||||||
|
.custom(custom)
|
||||||
|
.show();
|
||||||
|
dialog.setOnDismissListener(d -> finish());
|
||||||
|
if (input != null) {
|
||||||
|
input.post(() -> {
|
||||||
|
input.requestFocus();
|
||||||
|
final InputMethodManager imm =
|
||||||
|
(InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
|
||||||
|
if (imm != null) {
|
||||||
|
imm.showSoftInput(input, InputMethodManager.SHOW_IMPLICIT);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setResult(RESULT_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void submitReply(final Intent sourceIntent, final EditText input) {
|
||||||
|
if (input == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final String message = input.getText() == null ? "" : input.getText().toString().trim();
|
||||||
|
if (TextUtils.isEmpty(message)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sendReply(sourceIntent, message);
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void sendReply(final Intent sourceIntent, final String message) {
|
||||||
|
final Intent sendIntent = new Intent(this, NoConfirmationSmsSendService.class);
|
||||||
|
sendIntent.setAction(TelephonyManager.ACTION_RESPOND_VIA_MESSAGE);
|
||||||
|
sendIntent.putExtras(sourceIntent);
|
||||||
|
sendIntent.putExtra(Intent.EXTRA_TEXT, message);
|
||||||
|
sendIntent.setClipData(sourceIntent.getClipData());
|
||||||
|
startService(sendIntent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import android.graphics.Rect;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import androidx.activity.result.ActivityResultLauncher;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
import com.android.messaging.Factory;
|
import com.android.messaging.Factory;
|
||||||
@@ -69,7 +70,7 @@ public abstract class UIIntents {
|
|||||||
public static final String UI_INTENT_EXTRA_WITH_CUSTOM_TRANSITION = "with_custom_transition";
|
public static final String UI_INTENT_EXTRA_WITH_CUSTOM_TRANSITION = "with_custom_transition";
|
||||||
|
|
||||||
public static final String ACTION_RESET_NOTIFICATIONS =
|
public static final String ACTION_RESET_NOTIFICATIONS =
|
||||||
"com.bnkode.notie.reset_notifications";
|
"com.filabcorp.msgq.reset_notifications";
|
||||||
|
|
||||||
// Sending VCard uri to VCard detail activity
|
// Sending VCard uri to VCard detail activity
|
||||||
public static final String UI_INTENT_EXTRA_VCARD_URI = "vcard_uri";
|
public static final String UI_INTENT_EXTRA_VCARD_URI = "vcard_uri";
|
||||||
@@ -102,13 +103,18 @@ public abstract class UIIntents {
|
|||||||
// Sending attachment content type from widget
|
// Sending attachment content type from widget
|
||||||
public static final String UI_INTENT_EXTRA_ATTACHMENT_TYPE = "attachment_type";
|
public static final String UI_INTENT_EXTRA_ATTACHMENT_TYPE = "attachment_type";
|
||||||
|
|
||||||
|
public static final String UI_INTENT_EXTRA_LOCATION_TEXT = "location_text";
|
||||||
|
public static final String UI_INTENT_EXTRA_LOCATION_IMAGE_URI = "location_image_uri";
|
||||||
|
|
||||||
public static final String ACTION_WIDGET_CONVERSATION =
|
public static final String ACTION_WIDGET_CONVERSATION =
|
||||||
"com.bnkode.notie.widget_conversation:";
|
"com.filabcorp.msgq.widget_conversation:";
|
||||||
|
|
||||||
public static final String UI_INTENT_EXTRA_REQUIRES_MMS = "requires_mms";
|
public static final String UI_INTENT_EXTRA_REQUIRES_MMS = "requires_mms";
|
||||||
|
|
||||||
public static final String UI_INTENT_EXTRA_SELF_ID = "self_id";
|
public static final String UI_INTENT_EXTRA_SELF_ID = "self_id";
|
||||||
|
|
||||||
|
public static final String UI_INTENT_EXTRA_CONVERSATION_NAME = "conversation_name";
|
||||||
|
|
||||||
// Message position to scroll to.
|
// Message position to scroll to.
|
||||||
public static final String UI_INTENT_EXTRA_MESSAGE_POSITION = "message_position";
|
public static final String UI_INTENT_EXTRA_MESSAGE_POSITION = "message_position";
|
||||||
|
|
||||||
@@ -220,6 +226,12 @@ public abstract class UIIntents {
|
|||||||
*/
|
*/
|
||||||
public abstract void launchHomeAutomationCreateActivity(final Context context);
|
public abstract void launchHomeAutomationCreateActivity(final Context context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Launch home UI to pick a location to share in compose.
|
||||||
|
*/
|
||||||
|
public abstract void launchHomeLocationActivity(final Activity activity,
|
||||||
|
final ActivityResultLauncher<Intent> launcher);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Launch an activity to show a class zero message
|
* Launch an activity to show a class zero message
|
||||||
*/
|
*/
|
||||||
@@ -315,7 +327,8 @@ public abstract class UIIntents {
|
|||||||
*/
|
*/
|
||||||
public abstract PendingIntent getPendingIntentForSendingMessageToConversation(
|
public abstract PendingIntent getPendingIntentForSendingMessageToConversation(
|
||||||
final Context context, final String conversationId, final String selfId,
|
final Context context, final String conversationId, final String selfId,
|
||||||
final boolean requiresMms, final int requestCode);
|
final boolean requiresMms, final CharSequence conversationName,
|
||||||
|
final int requestCode);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a PendingIntent for clearing notifications.
|
* Get a PendingIntent for clearing notifications.
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import android.provider.MediaStore;
|
|||||||
import android.support.v7.mms.pdu.ContentType;
|
import android.support.v7.mms.pdu.ContentType;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import androidx.activity.result.ActivityResultLauncher;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.app.TaskStackBuilder;
|
import androidx.core.app.TaskStackBuilder;
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||||
@@ -248,6 +249,19 @@ public class UIIntentsImpl extends UIIntents {
|
|||||||
context.startActivity(intent);
|
context.startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void launchHomeLocationActivity(final Activity activity,
|
||||||
|
final ActivityResultLauncher<Intent> launcher) {
|
||||||
|
final Intent intent = new Intent(activity,
|
||||||
|
com.android.messaging.ui.home.HomeLocationActivity.class);
|
||||||
|
if (launcher != null) {
|
||||||
|
launcher.launch(intent);
|
||||||
|
} else {
|
||||||
|
activity.startActivity(intent);
|
||||||
|
}
|
||||||
|
activity.overridePendingTransition(R.anim.home_activity_open, R.anim.home_activity_hold);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void launchHomeProfileActivity(final Activity activity, final String conversationId) {
|
public void launchHomeProfileActivity(final Activity activity, final String conversationId) {
|
||||||
final Intent intent = new Intent(activity,
|
final Intent intent = new Intent(activity,
|
||||||
@@ -396,7 +410,7 @@ public class UIIntentsImpl extends UIIntents {
|
|||||||
@Override
|
@Override
|
||||||
public PendingIntent getPendingIntentForSendingMessageToConversation(final Context context,
|
public PendingIntent getPendingIntentForSendingMessageToConversation(final Context context,
|
||||||
final String conversationId, final String selfId, final boolean requiresMms,
|
final String conversationId, final String selfId, final boolean requiresMms,
|
||||||
final int requestCode) {
|
final CharSequence conversationName, final int requestCode) {
|
||||||
final Intent intent = new Intent(context, RemoteInputEntrypointActivity.class);
|
final Intent intent = new Intent(context, RemoteInputEntrypointActivity.class);
|
||||||
intent.setAction(Intent.ACTION_SENDTO);
|
intent.setAction(Intent.ACTION_SENDTO);
|
||||||
// Ensure that the platform doesn't reuse PendingIntents across conversations
|
// Ensure that the platform doesn't reuse PendingIntents across conversations
|
||||||
@@ -404,6 +418,10 @@ public class UIIntentsImpl extends UIIntents {
|
|||||||
intent.putExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID, conversationId);
|
intent.putExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID, conversationId);
|
||||||
intent.putExtra(UIIntents.UI_INTENT_EXTRA_SELF_ID, selfId);
|
intent.putExtra(UIIntents.UI_INTENT_EXTRA_SELF_ID, selfId);
|
||||||
intent.putExtra(UIIntents.UI_INTENT_EXTRA_REQUIRES_MMS, requiresMms);
|
intent.putExtra(UIIntents.UI_INTENT_EXTRA_REQUIRES_MMS, requiresMms);
|
||||||
|
if (!TextUtils.isEmpty(conversationName)) {
|
||||||
|
intent.putExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_NAME,
|
||||||
|
conversationName.toString());
|
||||||
|
}
|
||||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||||
return getPendingIntentWithParentStack(context, intent, requestCode);
|
return getPendingIntentWithParentStack(context, intent, requestCode);
|
||||||
}
|
}
|
||||||
|
|||||||
+159
-33
@@ -24,24 +24,37 @@ import android.content.Intent;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.activity.result.ActivityResultLauncher;
|
import androidx.activity.result.ActivityResultLauncher;
|
||||||
import androidx.activity.result.contract.ActivityResultContracts;
|
import androidx.activity.result.contract.ActivityResultContracts;
|
||||||
|
|
||||||
import com.android.messaging.R;
|
import com.android.messaging.R;
|
||||||
import com.android.messaging.ui.BugleActionBarActivity;
|
import com.android.messaging.ui.BugleActionBarActivity;
|
||||||
import com.android.messaging.ui.LicenseActivity;
|
import com.android.messaging.ui.LicenseActivity;
|
||||||
import com.android.messaging.ui.UIIntents;
|
import com.android.messaging.ui.UIIntents;
|
||||||
|
import com.android.messaging.ui.home.HomeAppLock;
|
||||||
|
import com.android.messaging.ui.home.HomeBottomSheet;
|
||||||
|
import com.android.messaging.ui.home.HomeSecuritySettingsActivity;
|
||||||
import com.android.messaging.ui.home.HomeSettingsRow;
|
import com.android.messaging.ui.home.HomeSettingsRow;
|
||||||
import com.android.messaging.ui.home.HomeSubHeader;
|
import com.android.messaging.ui.home.HomeSubHeader;
|
||||||
|
import com.android.messaging.ui.home.HomeToast;
|
||||||
import com.android.messaging.util.BuglePrefs;
|
import com.android.messaging.util.BuglePrefs;
|
||||||
import com.android.messaging.util.ChangeDefaultSmsAppHelper;
|
import com.android.messaging.util.ChangeDefaultSmsAppHelper;
|
||||||
import com.android.messaging.util.PhoneUtils;
|
import com.android.messaging.util.PhoneUtils;
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetDialog;
|
||||||
|
|
||||||
public class ApplicationSettingsActivity extends BugleActionBarActivity {
|
public class ApplicationSettingsActivity extends BugleActionBarActivity {
|
||||||
|
|
||||||
|
private static final String LANGUAGE_SYSTEM = "system";
|
||||||
|
private static final String LANGUAGE_EN = "en";
|
||||||
|
private static final String LANGUAGE_KO = "ko";
|
||||||
|
|
||||||
private View mSmsRow;
|
private View mSmsRow;
|
||||||
|
private View mLanguageRow;
|
||||||
|
private String mLanguageTag = LANGUAGE_SYSTEM;
|
||||||
private ChangeDefaultSmsAppHelper mChangeDefaultSmsAppHelper;
|
private ChangeDefaultSmsAppHelper mChangeDefaultSmsAppHelper;
|
||||||
private final ActivityResultLauncher<Intent> mLauncher = registerForActivityResult(
|
private final ActivityResultLauncher<Intent> mLauncher = registerForActivityResult(
|
||||||
new ActivityResultContracts.StartActivityForResult(), result -> {
|
new ActivityResultContracts.StartActivityForResult(), result -> {
|
||||||
@@ -64,6 +77,9 @@ public class ApplicationSettingsActivity extends BugleActionBarActivity {
|
|||||||
HomeSubHeader.bind(this, R.string.home_settings_title, v -> finish());
|
HomeSubHeader.bind(this, R.string.home_settings_title, v -> finish());
|
||||||
|
|
||||||
mSmsRow = findViewById(R.id.home_settings_sms);
|
mSmsRow = findViewById(R.id.home_settings_sms);
|
||||||
|
mLanguageRow = findViewById(R.id.home_settings_language);
|
||||||
|
mLanguageTag = BuglePrefs.getApplicationPrefs().getString(
|
||||||
|
getString(R.string.language_pref_key), LANGUAGE_SYSTEM);
|
||||||
bindStaticRows();
|
bindStaticRows();
|
||||||
updateSmsRow();
|
updateSmsRow();
|
||||||
}
|
}
|
||||||
@@ -72,6 +88,7 @@ public class ApplicationSettingsActivity extends BugleActionBarActivity {
|
|||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
updateSmsRow();
|
updateSmsRow();
|
||||||
|
bindAppLockRow();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -82,8 +99,9 @@ public class ApplicationSettingsActivity extends BugleActionBarActivity {
|
|||||||
|
|
||||||
private void bindStaticRows() {
|
private void bindStaticRows() {
|
||||||
final BuglePrefs prefs = BuglePrefs.getApplicationPrefs();
|
final BuglePrefs prefs = BuglePrefs.getApplicationPrefs();
|
||||||
final boolean topLevel = getIntent().getBooleanExtra(
|
|
||||||
UIIntents.UI_INTENT_EXTRA_TOP_LEVEL_SETTINGS, false);
|
bindComingSoon(R.id.home_settings_premium, R.drawable.ic_home_star_line,
|
||||||
|
R.string.home_settings_premium, R.string.home_settings_premium_subtitle);
|
||||||
|
|
||||||
HomeSettingsRow.bind(findViewById(R.id.home_settings_notifications),
|
HomeSettingsRow.bind(findViewById(R.id.home_settings_notifications),
|
||||||
R.drawable.ic_home_bell,
|
R.drawable.ic_home_bell,
|
||||||
@@ -96,6 +114,36 @@ public class ApplicationSettingsActivity extends BugleActionBarActivity {
|
|||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
}, null);
|
}, null);
|
||||||
|
|
||||||
|
bindRow(R.id.home_settings_sms_mms, R.drawable.ic_home_nav_messages,
|
||||||
|
R.string.home_settings_sms_mms, R.string.home_settings_sms_mms_subtitle,
|
||||||
|
v -> {
|
||||||
|
startActivity(UIIntents.get().getAdvancedSettingsIntent(this));
|
||||||
|
overridePendingTransition(R.anim.home_activity_open, R.anim.home_activity_hold);
|
||||||
|
});
|
||||||
|
|
||||||
|
bindComingSoon(R.id.home_settings_appearance, R.drawable.ic_home_edit,
|
||||||
|
R.string.home_settings_appearance, R.string.home_settings_appearance_subtitle);
|
||||||
|
|
||||||
|
bindLanguageRow();
|
||||||
|
bindAppLockRow();
|
||||||
|
|
||||||
|
final String automationKey = getString(R.string.automation_only_pref_key);
|
||||||
|
final boolean automationOnly = prefs.getBoolean(automationKey,
|
||||||
|
getResources().getBoolean(R.bool.automation_only_pref_default));
|
||||||
|
HomeSettingsRow.bind(findViewById(R.id.home_settings_automation_only),
|
||||||
|
R.drawable.ic_home_nav_automate,
|
||||||
|
getString(R.string.home_settings_automation_only),
|
||||||
|
getString(R.string.home_settings_automation_only_subtitle),
|
||||||
|
false, true, automationOnly,
|
||||||
|
null, (button, checked) -> prefs.putBoolean(automationKey, checked));
|
||||||
|
|
||||||
|
bindRow(R.id.home_settings_automations, R.drawable.ic_home_checklist,
|
||||||
|
R.string.home_settings_automations, R.string.home_settings_automations_subtitle,
|
||||||
|
v -> {
|
||||||
|
UIIntents.get().launchHomeAutomationActivity(this);
|
||||||
|
overridePendingTransition(R.anim.home_activity_open, R.anim.home_activity_hold);
|
||||||
|
});
|
||||||
|
|
||||||
final String sendKey = getString(R.string.send_sound_pref_key);
|
final String sendKey = getString(R.string.send_sound_pref_key);
|
||||||
final boolean sendSound = prefs.getBoolean(sendKey,
|
final boolean sendSound = prefs.getBoolean(sendKey,
|
||||||
getResources().getBoolean(R.bool.send_sound_pref_default));
|
getResources().getBoolean(R.bool.send_sound_pref_default));
|
||||||
@@ -105,41 +153,119 @@ public class ApplicationSettingsActivity extends BugleActionBarActivity {
|
|||||||
null, false, true, sendSound,
|
null, false, true, sendSound,
|
||||||
null, (button, checked) -> prefs.putBoolean(sendKey, checked));
|
null, (button, checked) -> prefs.putBoolean(sendKey, checked));
|
||||||
|
|
||||||
final String swipeKey = getString(R.string.swipe_right_deletes_conversation_key);
|
bindRow(R.id.home_settings_blocked, R.drawable.ic_home_menu_spam,
|
||||||
final boolean swipeDelete = prefs.getBoolean(swipeKey, false);
|
R.string.home_settings_blocked, R.string.home_settings_blocked_subtitle,
|
||||||
HomeSettingsRow.bind(findViewById(R.id.home_settings_swipe_delete),
|
v -> {
|
||||||
R.drawable.ic_home_menu_trash,
|
UIIntents.get().launchHomeSpamActivity(this);
|
||||||
getString(R.string.home_settings_swipe_delete),
|
overridePendingTransition(R.anim.home_activity_open, R.anim.home_activity_hold);
|
||||||
getString(R.string.home_settings_swipe_delete_subtitle),
|
});
|
||||||
false, true, swipeDelete,
|
|
||||||
null, (button, checked) -> prefs.putBoolean(swipeKey, checked));
|
|
||||||
|
|
||||||
final View advancedCard = findViewById(R.id.home_settings_advanced_card);
|
bindComingSoon(R.id.home_settings_privacy, R.drawable.ic_home_menu_person,
|
||||||
if (topLevel) {
|
R.string.home_settings_privacy, R.string.home_settings_privacy_subtitle);
|
||||||
advancedCard.setVisibility(View.VISIBLE);
|
bindComingSoon(R.id.home_settings_backup, R.drawable.ic_home_archive_line,
|
||||||
HomeSettingsRow.bind(findViewById(R.id.home_settings_advanced),
|
R.string.home_settings_backup, R.string.home_settings_backup_subtitle);
|
||||||
R.drawable.ic_home_nav_settings,
|
bindComingSoon(R.id.home_settings_data, R.drawable.ic_home_file_line,
|
||||||
getString(R.string.home_settings_advanced),
|
R.string.home_settings_data, R.string.home_settings_data_subtitle);
|
||||||
null, true, false, false,
|
bindComingSoon(R.id.home_settings_help, R.drawable.ic_home_info,
|
||||||
v -> {
|
R.string.home_settings_help, R.string.home_settings_help_subtitle);
|
||||||
startActivity(UIIntents.get().getAdvancedSettingsIntent(this));
|
|
||||||
overridePendingTransition(R.anim.home_activity_open,
|
|
||||||
R.anim.home_activity_hold);
|
|
||||||
},
|
|
||||||
null);
|
|
||||||
} else {
|
|
||||||
advancedCard.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
HomeSettingsRow.bind(findViewById(R.id.home_settings_licenses),
|
bindRow(R.id.home_settings_licenses, R.drawable.ic_info_light,
|
||||||
R.drawable.ic_info_light,
|
R.string.home_settings_licenses, 0,
|
||||||
getString(R.string.home_settings_licenses),
|
|
||||||
null, true, false, false,
|
|
||||||
v -> {
|
v -> {
|
||||||
startActivity(new Intent(this, LicenseActivity.class));
|
startActivity(new Intent(this, LicenseActivity.class));
|
||||||
overridePendingTransition(R.anim.home_activity_open, R.anim.home_activity_hold);
|
overridePendingTransition(R.anim.home_activity_open, R.anim.home_activity_hold);
|
||||||
},
|
});
|
||||||
null);
|
}
|
||||||
|
|
||||||
|
private void bindLanguageRow() {
|
||||||
|
if (mLanguageRow == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
HomeSettingsRow.bind(mLanguageRow, R.drawable.ic_home_language,
|
||||||
|
getString(R.string.home_settings_language), null, true, false, false,
|
||||||
|
v -> showLanguageSheet(), null);
|
||||||
|
HomeSettingsRow.setValue(mLanguageRow, languageLabel(mLanguageTag));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bindAppLockRow() {
|
||||||
|
final View row = findViewById(R.id.home_settings_app_lock);
|
||||||
|
if (row == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
HomeSettingsRow.bind(row, R.drawable.ic_home_lock,
|
||||||
|
getString(R.string.home_settings_app_lock),
|
||||||
|
getString(R.string.home_settings_app_lock_subtitle),
|
||||||
|
true, false, false,
|
||||||
|
v -> {
|
||||||
|
startActivity(new Intent(this, HomeSecuritySettingsActivity.class));
|
||||||
|
overridePendingTransition(R.anim.home_activity_open, R.anim.home_activity_hold);
|
||||||
|
}, null);
|
||||||
|
HomeSettingsRow.setValue(row, getString(HomeAppLock.isEnabled()
|
||||||
|
? R.string.home_lock_on : R.string.home_lock_off));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String languageLabel(final String tag) {
|
||||||
|
if (LANGUAGE_EN.equals(tag)) {
|
||||||
|
return getString(R.string.home_language_english);
|
||||||
|
}
|
||||||
|
if (LANGUAGE_KO.equals(tag)) {
|
||||||
|
return getString(R.string.home_language_korean);
|
||||||
|
}
|
||||||
|
return getString(R.string.home_language_system);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showLanguageSheet() {
|
||||||
|
final LayoutInflater inflater = LayoutInflater.from(this);
|
||||||
|
final LinearLayout list = (LinearLayout) inflater.inflate(
|
||||||
|
R.layout.home_language_sheet, null, false);
|
||||||
|
final BottomSheetDialog[] dialog = new BottomSheetDialog[1];
|
||||||
|
addLanguageOption(inflater, list, LANGUAGE_SYSTEM, R.string.home_language_system, dialog);
|
||||||
|
addLanguageOption(inflater, list, LANGUAGE_EN, R.string.home_language_english, dialog);
|
||||||
|
addLanguageOption(inflater, list, LANGUAGE_KO, R.string.home_language_korean, dialog);
|
||||||
|
dialog[0] = HomeBottomSheet.builder(this)
|
||||||
|
.title(R.string.home_settings_language)
|
||||||
|
.custom(list)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addLanguageOption(final LayoutInflater inflater, final LinearLayout parent,
|
||||||
|
final String tag, final int titleRes, final BottomSheetDialog[] dialog) {
|
||||||
|
final View row = inflater.inflate(R.layout.home_language_row, parent, false);
|
||||||
|
((TextView) row.findViewById(R.id.home_language_title)).setText(titleRes);
|
||||||
|
row.findViewById(R.id.home_language_check)
|
||||||
|
.setVisibility(tag.equals(mLanguageTag) ? View.VISIBLE : View.GONE);
|
||||||
|
row.setOnClickListener(v -> {
|
||||||
|
mLanguageTag = tag;
|
||||||
|
BuglePrefs.getApplicationPrefs().putString(
|
||||||
|
getString(R.string.language_pref_key), tag);
|
||||||
|
bindLanguageRow();
|
||||||
|
if (dialog[0] != null) {
|
||||||
|
dialog[0].dismiss();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
final LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||||
|
params.topMargin = parent.getChildCount() == 0
|
||||||
|
? 0 : getResources().getDimensionPixelSize(R.dimen.looply_space_sm);
|
||||||
|
row.setLayoutParams(params);
|
||||||
|
parent.addView(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bindComingSoon(final int rowId, final int iconRes, final int titleRes,
|
||||||
|
final int subtitleRes) {
|
||||||
|
bindRow(rowId, iconRes, titleRes, subtitleRes,
|
||||||
|
v -> HomeToast.show(this, R.string.home_popover_coming_soon));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bindRow(final int rowId, final int iconRes, final int titleRes,
|
||||||
|
final int subtitleRes, final View.OnClickListener clickListener) {
|
||||||
|
final View row = findViewById(rowId);
|
||||||
|
if (row == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
HomeSettingsRow.bind(row, iconRes, getString(titleRes),
|
||||||
|
subtitleRes == 0 ? null : getString(subtitleRes),
|
||||||
|
true, false, false, clickListener, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateSmsRow() {
|
private void updateSmsRow() {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import android.content.Context;
|
|||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.provider.ContactsContract.CommonDataKinds.Phone;
|
import android.provider.ContactsContract.CommonDataKinds.Phone;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.View.OnClickListener;
|
import android.view.View.OnClickListener;
|
||||||
@@ -53,9 +54,19 @@ public class ContactListItemView extends LinearLayout implements OnClickListener
|
|||||||
private ContactIconView mContactIconView;
|
private ContactIconView mContactIconView;
|
||||||
private ImageView mContactCheckmarkView;
|
private ImageView mContactCheckmarkView;
|
||||||
private ImageView mWorkProfileIcon;
|
private ImageView mWorkProfileIcon;
|
||||||
|
private View mSectionCard;
|
||||||
|
private View mSectionDivider;
|
||||||
|
private View mSectionGapTop;
|
||||||
|
private View mSectionGapBottom;
|
||||||
private HostInterface mHostInterface;
|
private HostInterface mHostInterface;
|
||||||
private boolean mShouldShowAlphabetHeader;
|
private boolean mShouldShowAlphabetHeader;
|
||||||
|
|
||||||
|
public static final int SECTION_NONE = 0;
|
||||||
|
public static final int SECTION_SINGLE = 1;
|
||||||
|
public static final int SECTION_FIRST = 2;
|
||||||
|
public static final int SECTION_MIDDLE = 3;
|
||||||
|
public static final int SECTION_LAST = 4;
|
||||||
|
|
||||||
public ContactListItemView(final Context context, final AttributeSet attrs) {
|
public ContactListItemView(final Context context, final AttributeSet attrs) {
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
mData = DataModel.get().createContactListItemData();
|
mData = DataModel.get().createContactListItemData();
|
||||||
@@ -71,6 +82,55 @@ public class ContactListItemView extends LinearLayout implements OnClickListener
|
|||||||
mContactIconView = findViewById(R.id.contact_icon);
|
mContactIconView = findViewById(R.id.contact_icon);
|
||||||
mContactCheckmarkView = findViewById(R.id.contact_checkmark);
|
mContactCheckmarkView = findViewById(R.id.contact_checkmark);
|
||||||
mWorkProfileIcon = findViewById(R.id.work_profile_icon);
|
mWorkProfileIcon = findViewById(R.id.work_profile_icon);
|
||||||
|
mSectionCard = findViewById(R.id.contact_section_card);
|
||||||
|
mSectionDivider = findViewById(R.id.contact_section_divider);
|
||||||
|
mSectionGapTop = findViewById(R.id.contact_section_gap_top);
|
||||||
|
mSectionGapBottom = findViewById(R.id.contact_section_gap_bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Draws this row as part of a letter-grouped card, or clears the chrome. */
|
||||||
|
public void setSectionBox(final int role) {
|
||||||
|
if (mSectionCard == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (role == SECTION_NONE) {
|
||||||
|
mSectionCard.setBackground(null);
|
||||||
|
mSectionCard.setClipToOutline(false);
|
||||||
|
if (mSectionDivider != null) {
|
||||||
|
mSectionDivider.setVisibility(GONE);
|
||||||
|
}
|
||||||
|
if (mSectionGapTop != null) {
|
||||||
|
mSectionGapTop.setVisibility(GONE);
|
||||||
|
}
|
||||||
|
if (mSectionGapBottom != null) {
|
||||||
|
mSectionGapBottom.setVisibility(GONE);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final int background;
|
||||||
|
if (role == SECTION_SINGLE) {
|
||||||
|
background = R.drawable.home_bg_contact_section_single;
|
||||||
|
} else if (role == SECTION_FIRST) {
|
||||||
|
background = R.drawable.home_bg_contact_section_top;
|
||||||
|
} else if (role == SECTION_LAST) {
|
||||||
|
background = R.drawable.home_bg_contact_section_bottom;
|
||||||
|
} else {
|
||||||
|
background = R.drawable.home_bg_contact_section_middle;
|
||||||
|
}
|
||||||
|
mSectionCard.setBackgroundResource(background);
|
||||||
|
mSectionCard.setClipToOutline(true);
|
||||||
|
if (mSectionDivider != null) {
|
||||||
|
mSectionDivider.setVisibility(
|
||||||
|
role == SECTION_FIRST || role == SECTION_MIDDLE ? VISIBLE : GONE);
|
||||||
|
}
|
||||||
|
if (mSectionGapTop != null) {
|
||||||
|
mSectionGapTop.setVisibility(
|
||||||
|
role == SECTION_FIRST || role == SECTION_SINGLE ? VISIBLE : GONE);
|
||||||
|
}
|
||||||
|
if (mSectionGapBottom != null) {
|
||||||
|
mSectionGapBottom.setVisibility(
|
||||||
|
role == SECTION_LAST || role == SECTION_SINGLE ? VISIBLE : GONE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -160,9 +220,11 @@ public class ContactListItemView extends LinearLayout implements OnClickListener
|
|||||||
} else {
|
} else {
|
||||||
mWorkProfileIcon.setVisibility(GONE);
|
mWorkProfileIcon.setVisibility(GONE);
|
||||||
}
|
}
|
||||||
if (mShouldShowAlphabetHeader) {
|
final String headerLabel = mData.getAlphabetHeader();
|
||||||
|
if (mShouldShowAlphabetHeader && !TextUtils.isEmpty(headerLabel)
|
||||||
|
&& !TextUtils.equals(headerLabel, " ")) {
|
||||||
mAlphabetHeaderTextView.setVisibility(VISIBLE);
|
mAlphabetHeaderTextView.setVisibility(VISIBLE);
|
||||||
mAlphabetHeaderTextView.setText(mData.getAlphabetHeader());
|
mAlphabetHeaderTextView.setText(headerLabel);
|
||||||
} else {
|
} else {
|
||||||
mAlphabetHeaderTextView.setVisibility(GONE);
|
mAlphabetHeaderTextView.setVisibility(GONE);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ public class ContactSectionIndexer implements SectionIndexer {
|
|||||||
if (TextUtils.isEmpty(mSections[i])) {
|
if (TextUtils.isEmpty(mSections[i])) {
|
||||||
mSections[i] = BLANK_HEADER_STRING;
|
mSections[i] = BLANK_HEADER_STRING;
|
||||||
} else if (!mSections[i].equals(BLANK_HEADER_STRING)) {
|
} else if (!mSections[i].equals(BLANK_HEADER_STRING)) {
|
||||||
mSections[i] = mSections[i].trim();
|
mSections[i] = normalizeProviderSection(mSections[i].trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
mSectionStartingPositions.add(position);
|
mSectionStartingPositions.add(position);
|
||||||
@@ -141,9 +141,11 @@ public class ContactSectionIndexer implements SectionIndexer {
|
|||||||
// The sort key is typically the contact's display name, so for example, a contact
|
// The sort key is typically the contact's display name, so for example, a contact
|
||||||
// named "Bob" will go into section "B". The Contacts provider generally uses a
|
// named "Bob" will go into section "B". The Contacts provider generally uses a
|
||||||
// a slightly more sophisticated heuristic, but as a fallback this is good enough.
|
// a slightly more sophisticated heuristic, but as a fallback this is good enough.
|
||||||
final String sortKey = cursor.getString(ContactUtil.INDEX_SORT_KEY);
|
String sortKey = cursor.getString(ContactUtil.INDEX_SORT_KEY);
|
||||||
final String section = TextUtils.isEmpty(sortKey) ? BLANK_HEADER_STRING :
|
if (TextUtils.isEmpty(sortKey)) {
|
||||||
sortKey.substring(0, 1).toUpperCase();
|
sortKey = cursor.getString(ContactUtil.INDEX_DISPLAY_NAME);
|
||||||
|
}
|
||||||
|
final String section = sectionLabelFor(sortKey);
|
||||||
|
|
||||||
final int lastIndex = sections.size() - 1;
|
final int lastIndex = sections.size() - 1;
|
||||||
final String currentSection = lastIndex >= 0 ? sections.get(lastIndex) : null;
|
final String currentSection = lastIndex >= 0 ? sections.get(lastIndex) : null;
|
||||||
@@ -158,6 +160,94 @@ public class ContactSectionIndexer implements SectionIndexer {
|
|||||||
sections.toArray(mSections);
|
sections.toArray(mSections);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Maps a Hangul syllable section from the provider to its choseong; leaves other labels. */
|
||||||
|
private static String normalizeProviderSection(final String title) {
|
||||||
|
if (title.codePointCount(0, title.length()) != 1) {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
final int codePoint = title.codePointAt(0);
|
||||||
|
if (codePoint >= 0xAC00 && codePoint <= 0xD7A3) {
|
||||||
|
return sectionLabelFor(title);
|
||||||
|
}
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Locale-aware bucket for a sort key: Latin A–Z, Hangul choseong (ㄱ…ㅎ), Japanese
|
||||||
|
* gojuon rows, other letters uppercased, and # for digits or symbols.
|
||||||
|
*/
|
||||||
|
static String sectionLabelFor(final String sortKey) {
|
||||||
|
if (TextUtils.isEmpty(sortKey)) {
|
||||||
|
return BLANK_HEADER_STRING;
|
||||||
|
}
|
||||||
|
int index = 0;
|
||||||
|
while (index < sortKey.length() && Character.isWhitespace(sortKey.charAt(index))) {
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
if (index >= sortKey.length()) {
|
||||||
|
return BLANK_HEADER_STRING;
|
||||||
|
}
|
||||||
|
final int codePoint = sortKey.codePointAt(index);
|
||||||
|
if (codePoint >= 0xAC00 && codePoint <= 0xD7A3) {
|
||||||
|
final int choseongIndex = (codePoint - 0xAC00) / (21 * 28);
|
||||||
|
return String.valueOf(HANGUL_CHOSEONG[choseongIndex]);
|
||||||
|
}
|
||||||
|
if ((codePoint >= 0x3131 && codePoint <= 0x314E)
|
||||||
|
|| (codePoint >= 0x1100 && codePoint <= 0x1112)) {
|
||||||
|
return new String(Character.toChars(codePoint));
|
||||||
|
}
|
||||||
|
final int kana = toHiragana(codePoint);
|
||||||
|
if (kana >= 0x3041 && kana <= 0x3096) {
|
||||||
|
return String.valueOf(japaneseRow(kana));
|
||||||
|
}
|
||||||
|
if (Character.isDigit(codePoint) || isCjkIdeograph(codePoint)
|
||||||
|
|| !Character.isLetter(codePoint)) {
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
return new String(Character.toChars(Character.toUpperCase(codePoint)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final char[] HANGUL_CHOSEONG = {
|
||||||
|
'ㄱ', 'ㄲ', 'ㄴ', 'ㄷ', 'ㄸ', 'ㄹ', 'ㅁ', 'ㅂ', 'ㅃ', 'ㅅ',
|
||||||
|
'ㅆ', 'ㅇ', 'ㅈ', 'ㅉ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ'
|
||||||
|
};
|
||||||
|
|
||||||
|
private static int toHiragana(final int codePoint) {
|
||||||
|
if (codePoint >= 0x30A1 && codePoint <= 0x30F6) {
|
||||||
|
return codePoint - 0x60;
|
||||||
|
}
|
||||||
|
return codePoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static char japaneseRow(final int hiragana) {
|
||||||
|
if (hiragana <= 0x304A) {
|
||||||
|
return 'あ';
|
||||||
|
} else if (hiragana <= 0x3054) {
|
||||||
|
return 'か';
|
||||||
|
} else if (hiragana <= 0x305E) {
|
||||||
|
return 'さ';
|
||||||
|
} else if (hiragana <= 0x3069) {
|
||||||
|
return 'た';
|
||||||
|
} else if (hiragana <= 0x306E) {
|
||||||
|
return 'な';
|
||||||
|
} else if (hiragana <= 0x307D) {
|
||||||
|
return 'は';
|
||||||
|
} else if (hiragana <= 0x3082) {
|
||||||
|
return 'ま';
|
||||||
|
} else if (hiragana <= 0x3088) {
|
||||||
|
return 'や';
|
||||||
|
} else if (hiragana <= 0x308D) {
|
||||||
|
return 'ら';
|
||||||
|
}
|
||||||
|
return 'わ';
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isCjkIdeograph(final int codePoint) {
|
||||||
|
return (codePoint >= 0x4E00 && codePoint <= 0x9FFF)
|
||||||
|
|| (codePoint >= 0x3400 && codePoint <= 0x4DBF)
|
||||||
|
|| (codePoint >= 0x20000 && codePoint <= 0x2A6DF);
|
||||||
|
}
|
||||||
|
|
||||||
private void buildIndexer(final Cursor cursor) {
|
private void buildIndexer(final Cursor cursor) {
|
||||||
// First check if we get indexer label extras from the contact provider; if not, fall back
|
// First check if we get indexer label extras from the contact provider; if not, fall back
|
||||||
// to building from display names.
|
// to building from display names.
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ package com.android.messaging.ui.conversation;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
@@ -53,6 +54,7 @@ import androidx.core.view.WindowInsetsControllerCompat;
|
|||||||
|
|
||||||
import com.android.messaging.Factory;
|
import com.android.messaging.Factory;
|
||||||
import com.android.messaging.R;
|
import com.android.messaging.R;
|
||||||
|
import com.android.messaging.datamodel.ScheduledMessageStore;
|
||||||
import com.android.messaging.datamodel.binding.Binding;
|
import com.android.messaging.datamodel.binding.Binding;
|
||||||
import com.android.messaging.datamodel.binding.BindingBase;
|
import com.android.messaging.datamodel.binding.BindingBase;
|
||||||
import com.android.messaging.datamodel.binding.ImmutableBindingRef;
|
import com.android.messaging.datamodel.binding.ImmutableBindingRef;
|
||||||
@@ -72,7 +74,9 @@ import com.android.messaging.ui.AttachmentPreview;
|
|||||||
import com.android.messaging.ui.BugleActionBarActivity;
|
import com.android.messaging.ui.BugleActionBarActivity;
|
||||||
import com.android.messaging.ui.PlainTextEditText;
|
import com.android.messaging.ui.PlainTextEditText;
|
||||||
import com.android.messaging.ui.conversation.ConversationInputManager.ConversationInputSink;
|
import com.android.messaging.ui.conversation.ConversationInputManager.ConversationInputSink;
|
||||||
|
import com.android.messaging.ui.UIIntents;
|
||||||
import com.android.messaging.ui.home.HomeComposeAttachPanel;
|
import com.android.messaging.ui.home.HomeComposeAttachPanel;
|
||||||
|
import com.android.messaging.ui.home.HomeSchedulePicker;
|
||||||
import com.android.messaging.ui.home.HomeToast;
|
import com.android.messaging.ui.home.HomeToast;
|
||||||
import com.android.messaging.ui.home.HomeUiMotion;
|
import com.android.messaging.ui.home.HomeUiMotion;
|
||||||
import com.android.messaging.util.AccessibilityUtil;
|
import com.android.messaging.util.AccessibilityUtil;
|
||||||
@@ -116,6 +120,8 @@ public class ComposeMessageView extends LinearLayout
|
|||||||
void showAttachmentChooser();
|
void showAttachmentChooser();
|
||||||
void launchSystemGalleryPicker();
|
void launchSystemGalleryPicker();
|
||||||
void requestMediaPermission(Runnable onGranted);
|
void requestMediaPermission(Runnable onGranted);
|
||||||
|
void requestLocationPermission(Runnable onGranted);
|
||||||
|
void launchLocationShare();
|
||||||
boolean shouldShowSubjectEditor();
|
boolean shouldShowSubjectEditor();
|
||||||
boolean shouldHideAttachmentsWhenSimSelectorShown();
|
boolean shouldHideAttachmentsWhenSimSelectorShown();
|
||||||
Uri getSelfSendButtonIconUri();
|
Uri getSelfSendButtonIconUri();
|
||||||
@@ -124,6 +130,7 @@ public class ComposeMessageView extends LinearLayout
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static final int CODEPOINTS_REMAINING_BEFORE_COUNTER_SHOWN = 10;
|
public static final int CODEPOINTS_REMAINING_BEFORE_COUNTER_SHOWN = 10;
|
||||||
|
private static final String KEY_SCHEDULED_SEND_AT = "home_scheduled_send_at";
|
||||||
|
|
||||||
// There is no draft and there is no need for the SIM selector
|
// There is no draft and there is no need for the SIM selector
|
||||||
private static final int SEND_WIDGET_MODE_SELF_AVATAR = 1;
|
private static final int SEND_WIDGET_MODE_SELF_AVATAR = 1;
|
||||||
@@ -150,6 +157,9 @@ public class ComposeMessageView extends LinearLayout
|
|||||||
private int mComposeBarPaddingBottom;
|
private int mComposeBarPaddingBottom;
|
||||||
private ImageButton mVoiceButton;
|
private ImageButton mVoiceButton;
|
||||||
private View mSendColumn;
|
private View mSendColumn;
|
||||||
|
private View mScheduleChipRow;
|
||||||
|
private TextView mScheduleChipText;
|
||||||
|
private long mScheduledSendAt;
|
||||||
|
|
||||||
private final Binding<DraftMessageData> mBinding;
|
private final Binding<DraftMessageData> mBinding;
|
||||||
private IComposeMessageViewHost mHost;
|
private IComposeMessageViewHost mHost;
|
||||||
@@ -322,6 +332,16 @@ public class ComposeMessageView extends LinearLayout
|
|||||||
mComposeBarPaddingBottom = mComposeBar.getPaddingBottom();
|
mComposeBarPaddingBottom = mComposeBar.getPaddingBottom();
|
||||||
}
|
}
|
||||||
mAttachHost = createComposeAttachHost();
|
mAttachHost = createComposeAttachHost();
|
||||||
|
mScheduleChipRow = findViewById(R.id.home_schedule_chip_row);
|
||||||
|
mScheduleChipText = findViewById(R.id.home_schedule_chip_text);
|
||||||
|
final View scheduleChip = findViewById(R.id.home_schedule_chip);
|
||||||
|
if (scheduleChip != null) {
|
||||||
|
scheduleChip.setOnClickListener(v -> showSchedulePicker());
|
||||||
|
}
|
||||||
|
final View scheduleClear = findViewById(R.id.home_schedule_chip_clear);
|
||||||
|
if (scheduleClear != null) {
|
||||||
|
scheduleClear.setOnClickListener(v -> setScheduledSendAt(0L));
|
||||||
|
}
|
||||||
mAttachMediaButton.setOnClickListener(clickView -> toggleComposeAttachmentSheet());
|
mAttachMediaButton.setOnClickListener(clickView -> toggleComposeAttachmentSheet());
|
||||||
mAttachMediaButton.setSelected(mComposeEditText.hasFocus());
|
mAttachMediaButton.setSelected(mComposeEditText.hasFocus());
|
||||||
|
|
||||||
@@ -433,6 +453,9 @@ public class ComposeMessageView extends LinearLayout
|
|||||||
mBinding.ensureBound(data);
|
mBinding.ensureBound(data);
|
||||||
switch (result) {
|
switch (result) {
|
||||||
case CheckDraftForSendTask.RESULT_PASSED:
|
case CheckDraftForSendTask.RESULT_PASSED:
|
||||||
|
if (queueScheduledMessageIfNeeded()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
// Continue sending after check succeeded.
|
// Continue sending after check succeeded.
|
||||||
final MessageData message = mBinding.getData()
|
final MessageData message = mBinding.getData()
|
||||||
.prepareMessageForSending(mBinding);
|
.prepareMessageForSending(mBinding);
|
||||||
@@ -1040,6 +1063,35 @@ public class ComposeMessageView extends LinearLayout
|
|||||||
|
|
||||||
public void saveInputState(final Bundle outState) {
|
public void saveInputState(final Bundle outState) {
|
||||||
mInputManager.onSaveInputState(outState);
|
mInputManager.onSaveInputState(outState);
|
||||||
|
outState.putLong(KEY_SCHEDULED_SEND_AT, mScheduledSendAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void restoreScheduledSendAt(final Bundle state) {
|
||||||
|
if (state == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setScheduledSendAt(state.getLong(KEY_SCHEDULED_SEND_AT, 0L));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void applySharedLocation(final Intent data) {
|
||||||
|
if (data == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final String text = data.getStringExtra(UIIntents.UI_INTENT_EXTRA_LOCATION_TEXT);
|
||||||
|
if (!TextUtils.isEmpty(text)) {
|
||||||
|
mComposeEditText.setText(text);
|
||||||
|
mComposeEditText.setSelection(text.length());
|
||||||
|
if (mBinding.isBound()) {
|
||||||
|
mBinding.getData().setMessageText(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final Uri image = data.getParcelableExtra(
|
||||||
|
UIIntents.UI_INTENT_EXTRA_LOCATION_IMAGE_URI, Uri.class);
|
||||||
|
if (image != null) {
|
||||||
|
onPendingAttachmentAdded(PendingAttachmentData.createPendingAttachmentData(
|
||||||
|
"image/jpeg", image));
|
||||||
|
}
|
||||||
|
resumeComposeMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void resetMediaPickerState() {
|
public void resetMediaPickerState() {
|
||||||
@@ -1135,6 +1187,18 @@ public class ComposeMessageView extends LinearLayout
|
|||||||
openMediaFromAttachmentSheet(ConversationInputManager.MEDIA_PICKER_TARGET_CONTACT);
|
openMediaFromAttachmentSheet(ConversationInputManager.MEDIA_PICKER_TARGET_CONTACT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onShareLocation() {
|
||||||
|
if (mHost != null) {
|
||||||
|
mHost.requestLocationPermission(() -> mHost.launchLocationShare());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onScheduleMessage() {
|
||||||
|
showSchedulePicker();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRequestMediaPermission() {
|
public void onRequestMediaPermission() {
|
||||||
if (mHost != null) {
|
if (mHost != null) {
|
||||||
@@ -1245,6 +1309,58 @@ public class ComposeMessageView extends LinearLayout
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean queueScheduledMessageIfNeeded() {
|
||||||
|
if (mScheduledSendAt <= System.currentTimeMillis()) {
|
||||||
|
if (mScheduledSendAt > 0L) {
|
||||||
|
setScheduledSendAt(0L);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final DraftMessageData draft = mBinding.getData();
|
||||||
|
final boolean empty = TextUtils.isEmpty(draft.getMessageText())
|
||||||
|
&& TextUtils.isEmpty(draft.getMessageSubject())
|
||||||
|
&& draft.getReadOnlyAttachments().isEmpty();
|
||||||
|
if (empty) {
|
||||||
|
HomeToast.show(getContext(), R.string.home_schedule_need_message);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
final MessageData message = draft.prepareMessageForSending(mBinding);
|
||||||
|
if (message == null || !message.hasContent()) {
|
||||||
|
HomeToast.show(getContext(), R.string.home_schedule_need_message);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
ScheduledMessageStore.schedule(getContext(), message, mScheduledSendAt);
|
||||||
|
hideSubjectEditor();
|
||||||
|
HomeToast.show(getContext(), getResources().getString(
|
||||||
|
R.string.home_schedule_queued, HomeSchedulePicker.formatChip(
|
||||||
|
getContext(), mScheduledSendAt)));
|
||||||
|
setScheduledSendAt(0L);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showSchedulePicker() {
|
||||||
|
final Activity activity = resolveActivity();
|
||||||
|
if (activity == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
HomeSchedulePicker.show(activity, mScheduledSendAt, this::setScheduledSendAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setScheduledSendAt(final long sendAt) {
|
||||||
|
mScheduledSendAt = sendAt > System.currentTimeMillis() ? sendAt : 0L;
|
||||||
|
if (mScheduleChipRow == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mScheduledSendAt <= 0L) {
|
||||||
|
mScheduleChipRow.setVisibility(GONE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mScheduleChipText != null) {
|
||||||
|
mScheduleChipText.setText(HomeSchedulePicker.formatChip(getContext(), mScheduledSendAt));
|
||||||
|
}
|
||||||
|
mScheduleChipRow.setVisibility(VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
private Activity resolveActivity() {
|
private Activity resolveActivity() {
|
||||||
if (mOriginalContext instanceof Activity) {
|
if (mOriginalContext instanceof Activity) {
|
||||||
return (Activity) mOriginalContext;
|
return (Activity) mOriginalContext;
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ import com.android.messaging.ui.ConversationDrawables;
|
|||||||
import com.android.messaging.ui.SnackBar;
|
import com.android.messaging.ui.SnackBar;
|
||||||
import com.android.messaging.ui.UIIntents;
|
import com.android.messaging.ui.UIIntents;
|
||||||
import com.android.messaging.ui.animation.PopupTransitionAnimation;
|
import com.android.messaging.ui.animation.PopupTransitionAnimation;
|
||||||
|
import com.android.messaging.ui.home.HomeBottomSheet;
|
||||||
import com.android.messaging.ui.home.HomeComposeAttachPanel;
|
import com.android.messaging.ui.home.HomeComposeAttachPanel;
|
||||||
import com.android.messaging.ui.home.HomeEmptyState;
|
import com.android.messaging.ui.home.HomeEmptyState;
|
||||||
import com.android.messaging.ui.home.HomeGallerySheet;
|
import com.android.messaging.ui.home.HomeGallerySheet;
|
||||||
@@ -131,10 +132,12 @@ import com.android.messaging.util.ChangeDefaultSmsAppHelper;
|
|||||||
import com.android.messaging.util.ContactUtil;
|
import com.android.messaging.util.ContactUtil;
|
||||||
import com.android.messaging.util.ImeUtil;
|
import com.android.messaging.util.ImeUtil;
|
||||||
import com.android.messaging.util.LogUtil;
|
import com.android.messaging.util.LogUtil;
|
||||||
|
import com.android.messaging.util.OsUtil;
|
||||||
import com.android.messaging.util.PhoneUtils;
|
import com.android.messaging.util.PhoneUtils;
|
||||||
import com.android.messaging.util.TextUtil;
|
import com.android.messaging.util.TextUtil;
|
||||||
import com.android.messaging.util.UiUtils;
|
import com.android.messaging.util.UiUtils;
|
||||||
import com.android.messaging.util.UriUtil;
|
import com.android.messaging.util.UriUtil;
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetDialog;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -336,6 +339,26 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
private Runnable mOnLocationPermissionGranted;
|
||||||
|
private final ActivityResultLauncher<String[]> mLocationPermissionLauncher =
|
||||||
|
registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(),
|
||||||
|
result -> {
|
||||||
|
final Runnable next = mOnLocationPermissionGranted;
|
||||||
|
mOnLocationPermissionGranted = null;
|
||||||
|
if (next != null) {
|
||||||
|
next.run();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
private final ActivityResultLauncher<Intent> mLocationShareLauncher =
|
||||||
|
registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),
|
||||||
|
result -> {
|
||||||
|
if (result.getResultCode() == Activity.RESULT_OK
|
||||||
|
&& result.getData() != null
|
||||||
|
&& mComposeMessageView != null) {
|
||||||
|
mComposeMessageView.applySharedLocation(result.getData());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
public ConversationFragment getConversationFragment() {
|
public ConversationFragment getConversationFragment() {
|
||||||
return (ConversationFragment) getParentFragmentManager().findFragmentByTag(
|
return (ConversationFragment) getParentFragmentManager().findFragmentByTag(
|
||||||
ConversationFragment.FRAGMENT_TAG);
|
ConversationFragment.FRAGMENT_TAG);
|
||||||
@@ -544,6 +567,7 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
|||||||
// Bind the compose message view to the DraftMessageData
|
// Bind the compose message view to the DraftMessageData
|
||||||
mComposeMessageView.bind(DataModel.get().createDraftMessageData(
|
mComposeMessageView.bind(DataModel.get().createDraftMessageData(
|
||||||
mBinding.getData().getConversationId()), this);
|
mBinding.getData().getConversationId()), this);
|
||||||
|
mComposeMessageView.restoreScheduledSendAt(savedInstanceState);
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
@@ -1508,13 +1532,9 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
|||||||
if (messageView != mSelectedMessage) {
|
if (messageView != mSelectedMessage) {
|
||||||
final ConversationMessageData data = messageView.getData();
|
final ConversationMessageData data = messageView.getData();
|
||||||
final boolean isReadyToSend = isReadyForAction();
|
final boolean isReadyToSend = isReadyForAction();
|
||||||
if (data.getOneClickResendMessage()) {
|
if (data.getOneClickResendMessage() || data.getShowResendMessage()) {
|
||||||
// Directly resend the message on tap if it's failed
|
// Resend / delete is offered from the status row under the bubble.
|
||||||
retrySend(data.getMessageId());
|
|
||||||
selectMessage(null);
|
selectMessage(null);
|
||||||
} else if (data.getShowResendMessage() && isReadyToSend) {
|
|
||||||
// Select the message to show the resend/download/delete options
|
|
||||||
selectMessage(messageView);
|
|
||||||
} else if (data.getShowDownloadMessage() && isReadyToSend) {
|
} else if (data.getShowDownloadMessage() && isReadyToSend) {
|
||||||
// Directly download the message on tap
|
// Directly download the message on tap
|
||||||
retryDownload(data.getMessageId());
|
retryDownload(data.getMessageId());
|
||||||
@@ -1530,6 +1550,43 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessageSendOptionsClick(final ConversationMessageView messageView) {
|
||||||
|
if (getActivity() == null || messageView == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final ConversationMessageData data = messageView.getData();
|
||||||
|
final String messageId = data.getMessageId();
|
||||||
|
if (TextUtils.isEmpty(messageId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final View custom = LayoutInflater.from(requireContext())
|
||||||
|
.inflate(R.layout.home_message_status_sheet, null, false);
|
||||||
|
final BottomSheetDialog[] dialog = { null };
|
||||||
|
final View resend = custom.findViewById(R.id.home_message_status_resend);
|
||||||
|
final View delete = custom.findViewById(R.id.home_message_status_delete);
|
||||||
|
if (resend != null) {
|
||||||
|
resend.setOnClickListener(v -> {
|
||||||
|
if (dialog[0] != null) {
|
||||||
|
dialog[0].dismiss();
|
||||||
|
}
|
||||||
|
retrySend(messageId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (delete != null) {
|
||||||
|
delete.setOnClickListener(v -> {
|
||||||
|
if (dialog[0] != null) {
|
||||||
|
dialog[0].dismiss();
|
||||||
|
}
|
||||||
|
deleteMessage(messageId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
dialog[0] = HomeBottomSheet.builder(requireContext())
|
||||||
|
.title(R.string.home_message_status_options_title)
|
||||||
|
.custom(custom)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
private static class AttachmentToSave {
|
private static class AttachmentToSave {
|
||||||
public final Uri uri;
|
public final Uri uri;
|
||||||
public final String contentType;
|
public final String contentType;
|
||||||
@@ -1762,6 +1819,23 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void requestLocationPermission(final Runnable onGranted) {
|
||||||
|
if (OsUtil.hasLocationPermission()) {
|
||||||
|
if (onGranted != null) {
|
||||||
|
onGranted.run();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mOnLocationPermissionGranted = onGranted;
|
||||||
|
mLocationPermissionLauncher.launch(OsUtil.getLocationPermissions());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void launchLocationShare() {
|
||||||
|
UIIntents.get().launchHomeLocationActivity(requireActivity(), mLocationShareLauncher);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showAttachmentChooser() {
|
public void showAttachmentChooser() {
|
||||||
showAttachmentChooser(mConversationId, getActivity());
|
showAttachmentChooser(mConversationId, getActivity());
|
||||||
|
|||||||
+85
-15
@@ -19,6 +19,7 @@ package com.android.messaging.ui.conversation;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
import android.graphics.Paint;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
@@ -37,6 +38,7 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.ImageView;
|
||||||
import android.widget.ImageView.ScaleType;
|
import android.widget.ImageView.ScaleType;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
@@ -87,6 +89,7 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick
|
|||||||
Rect imageBounds, boolean longPress);
|
Rect imageBounds, boolean longPress);
|
||||||
SubscriptionListEntry getSubscriptionEntryForSelfParticipant(String selfParticipantId,
|
SubscriptionListEntry getSubscriptionEntryForSelfParticipant(String selfParticipantId,
|
||||||
boolean excludeDefault);
|
boolean excludeDefault);
|
||||||
|
void onMessageSendOptionsClick(ConversationMessageView view);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final ConversationMessageData mData;
|
private final ConversationMessageData mData;
|
||||||
@@ -98,6 +101,8 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick
|
|||||||
private boolean mMessageTextHasLinks;
|
private boolean mMessageTextHasLinks;
|
||||||
private boolean mMessageHasYouTubeLink;
|
private boolean mMessageHasYouTubeLink;
|
||||||
private TextView mStatusTextView;
|
private TextView mStatusTextView;
|
||||||
|
private TextView mStatusActionView;
|
||||||
|
private ImageView mStatusIconView;
|
||||||
private TextView mTitleTextView;
|
private TextView mTitleTextView;
|
||||||
private TextView mMmsInfoTextView;
|
private TextView mMmsInfoTextView;
|
||||||
private LinearLayout mMessageTitleLayout;
|
private LinearLayout mMessageTitleLayout;
|
||||||
@@ -144,6 +149,16 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick
|
|||||||
IgnoreLinkLongClickHelper.ignoreLinkLongClick(mMessageTextView, this);
|
IgnoreLinkLongClickHelper.ignoreLinkLongClick(mMessageTextView, this);
|
||||||
|
|
||||||
mStatusTextView = findViewById(R.id.message_status);
|
mStatusTextView = findViewById(R.id.message_status);
|
||||||
|
mStatusActionView = findViewById(R.id.message_status_action);
|
||||||
|
mStatusIconView = findViewById(R.id.message_status_icon);
|
||||||
|
if (mStatusActionView != null) {
|
||||||
|
mStatusActionView.setPaintFlags(
|
||||||
|
mStatusActionView.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
|
||||||
|
mStatusActionView.setOnClickListener(v -> notifySendOptionsClick());
|
||||||
|
}
|
||||||
|
if (mStatusIconView != null) {
|
||||||
|
mStatusIconView.setOnClickListener(v -> notifySendOptionsClick());
|
||||||
|
}
|
||||||
mTitleTextView = findViewById(R.id.message_title);
|
mTitleTextView = findViewById(R.id.message_title);
|
||||||
mMmsInfoTextView = findViewById(R.id.mms_info);
|
mMmsInfoTextView = findViewById(R.id.mms_info);
|
||||||
mMessageTitleLayout = findViewById(R.id.message_title_layout);
|
mMessageTitleLayout = findViewById(R.id.message_title_layout);
|
||||||
@@ -298,6 +313,31 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick
|
|||||||
|| status == MessageData.BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER;
|
|| status == MessageData.BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isOutgoingWaitingStatus() {
|
||||||
|
if (mData == null || mData.getIsIncoming() || isOptimisticSending()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final int status = mData.getStatus();
|
||||||
|
return status == MessageData.BUGLE_STATUS_OUTGOING_AWAITING_RETRY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean shouldShowSendOptions() {
|
||||||
|
if (mData == null || mData.getIsIncoming() || isOptimisticSending()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!PhoneUtils.getDefault().isDefaultSmsApp()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return isOutgoingWaitingStatus()
|
||||||
|
|| mData.getStatus() == MessageData.BUGLE_STATUS_OUTGOING_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifySendOptionsClick() {
|
||||||
|
if (mHost != null && shouldShowSendOptions()) {
|
||||||
|
mHost.onMessageSendOptionsClick(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether we should show simplified visual style for the message view (i.e. hide the
|
* Returns whether we should show simplified visual style for the message view (i.e. hide the
|
||||||
* avatar and bubble arrow, reduce padding).
|
* avatar and bubble arrow, reduce padding).
|
||||||
@@ -367,10 +407,13 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick
|
|||||||
case MessageData.BUGLE_STATUS_OUTGOING_YET_TO_SEND:
|
case MessageData.BUGLE_STATUS_OUTGOING_YET_TO_SEND:
|
||||||
case MessageData.BUGLE_STATUS_OUTGOING_SENDING:
|
case MessageData.BUGLE_STATUS_OUTGOING_SENDING:
|
||||||
case MessageData.BUGLE_STATUS_OUTGOING_RESENDING:
|
case MessageData.BUGLE_STATUS_OUTGOING_RESENDING:
|
||||||
case MessageData.BUGLE_STATUS_OUTGOING_AWAITING_RETRY:
|
|
||||||
statusResId = R.string.message_status_sending;
|
statusResId = R.string.message_status_sending;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case MessageData.BUGLE_STATUS_OUTGOING_AWAITING_RETRY:
|
||||||
|
statusText = getResources().getString(R.string.home_message_status_waiting);
|
||||||
|
break;
|
||||||
|
|
||||||
case MessageData.BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER:
|
case MessageData.BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER:
|
||||||
statusResId = R.string.message_status_send_failed_emergency_number;
|
statusResId = R.string.message_status_send_failed_emergency_number;
|
||||||
break;
|
break;
|
||||||
@@ -382,12 +425,7 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (PhoneUtils.getDefault().isDefaultSmsApp()) {
|
if (PhoneUtils.getDefault().isDefaultSmsApp()) {
|
||||||
if (isSelected()) {
|
statusText = getResources().getString(R.string.home_message_status_not_sent);
|
||||||
statusResId = R.string.message_status_resend;
|
|
||||||
} else {
|
|
||||||
statusResId = MmsUtils.mapRawStatusToErrorResourceId(
|
|
||||||
mData.getStatus(), mData.getRawTelephonyStatus());
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// FALL THROUGH HERE
|
// FALL THROUGH HERE
|
||||||
@@ -443,10 +481,15 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick
|
|||||||
// We set the text even if the view will be GONE for accessibility
|
// We set the text even if the view will be GONE for accessibility
|
||||||
mStatusTextView.setText(statusText);
|
mStatusTextView.setText(statusText);
|
||||||
final boolean statusVisible = !TextUtils.isEmpty(statusText);
|
final boolean statusVisible = !TextUtils.isEmpty(statusText);
|
||||||
if (statusVisible) {
|
mStatusTextView.setVisibility(statusVisible ? View.VISIBLE : View.GONE);
|
||||||
mStatusTextView.setVisibility(View.VISIBLE);
|
|
||||||
} else {
|
final boolean sendOptionsVisible = shouldShowSendOptions();
|
||||||
mStatusTextView.setVisibility(View.GONE);
|
if (mStatusActionView != null) {
|
||||||
|
mStatusActionView.setVisibility(sendOptionsVisible ? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
|
if (mStatusIconView != null) {
|
||||||
|
mStatusIconView.setVisibility(
|
||||||
|
isOutgoingWaitingStatus() ? View.VISIBLE : View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean deliveredBadgeVisible =
|
final boolean deliveredBadgeVisible =
|
||||||
@@ -477,11 +520,11 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick
|
|||||||
}
|
}
|
||||||
|
|
||||||
final boolean metadataVisible = senderNameVisible || statusVisible
|
final boolean metadataVisible = senderNameVisible || statusVisible
|
||||||
|| deliveredBadgeVisible || simNameVisible;
|
|| sendOptionsVisible || deliveredBadgeVisible || simNameVisible;
|
||||||
mMessageMetadataView.setVisibility(metadataVisible ? View.VISIBLE : View.GONE);
|
mMessageMetadataView.setVisibility(metadataVisible ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
final boolean messageTextAndOrInfoVisible = titleVisible || subjectVisible
|
final boolean messageTextAndOrInfoVisible = titleVisible || subjectVisible
|
||||||
|| mData.hasText() || metadataVisible;
|
|| mData.hasText();
|
||||||
mMessageTextAndInfoView.setVisibility(
|
mMessageTextAndInfoView.setVisibility(
|
||||||
messageTextAndOrInfoVisible ? View.VISIBLE : View.GONE);
|
messageTextAndOrInfoVisible ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
@@ -787,6 +830,9 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick
|
|||||||
updateMessageAttachmentsAppearance(gravity);
|
updateMessageAttachmentsAppearance(gravity);
|
||||||
|
|
||||||
mMessageMetadataView.setPadding(0, metadataTopPadding, 0, 0);
|
mMessageMetadataView.setPadding(0, metadataTopPadding, 0, 0);
|
||||||
|
if (mMessageMetadataView.getVisibility() == View.VISIBLE) {
|
||||||
|
setPadding(getPaddingStart(), getPaddingTop(), getPaddingEnd(), metadataTopPadding);
|
||||||
|
}
|
||||||
|
|
||||||
updateTextAppearance();
|
updateTextAppearance();
|
||||||
|
|
||||||
@@ -844,6 +890,10 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick
|
|||||||
description.append(separator);
|
description.append(separator);
|
||||||
description.append(mStatusTextView.getText());
|
description.append(mStatusTextView.getText());
|
||||||
}
|
}
|
||||||
|
if (mStatusActionView != null && mStatusActionView.getVisibility() == View.VISIBLE) {
|
||||||
|
description.append(separator);
|
||||||
|
description.append(mStatusActionView.getText());
|
||||||
|
}
|
||||||
|
|
||||||
if (mSimNameView.getVisibility() == View.VISIBLE) {
|
if (mSimNameView.getVisibility() == View.VISIBLE) {
|
||||||
description.append(separator);
|
description.append(separator);
|
||||||
@@ -995,10 +1045,30 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick
|
|||||||
mData.hasAttachments() && !shouldShowMessageTextBubble()) {
|
mData.hasAttachments() && !shouldShowMessageTextBubble()) {
|
||||||
timestampColorResId = R.color.timestamp_text_outgoing;
|
timestampColorResId = R.color.timestamp_text_outgoing;
|
||||||
}
|
}
|
||||||
mStatusTextView.setTextColor(getResources().getColor(timestampColorResId, theme));
|
// Status captions sit outside the bubble, so keep them readable against the canvas.
|
||||||
|
final int captionColorResId;
|
||||||
|
if (shouldShowSendOptions() && !isOutgoingWaitingStatus()) {
|
||||||
|
captionColorResId = R.color.home_destructive;
|
||||||
|
} else if (isOutgoingWaitingStatus() || (!mData.getIsIncoming()
|
||||||
|
&& (mData.getStatus() == MessageData.BUGLE_STATUS_OUTGOING_SENDING
|
||||||
|
|| mData.getStatus() == MessageData.BUGLE_STATUS_OUTGOING_RESENDING
|
||||||
|
|| isOptimisticSending()))) {
|
||||||
|
captionColorResId = R.color.home_text_secondary;
|
||||||
|
} else {
|
||||||
|
captionColorResId = mData.getIsIncoming()
|
||||||
|
? R.color.timestamp_text_incoming : R.color.timestamp_text_outgoing;
|
||||||
|
}
|
||||||
|
final int captionColor = getResources().getColor(captionColorResId, theme);
|
||||||
|
mStatusTextView.setTextColor(captionColor);
|
||||||
|
if (mStatusActionView != null) {
|
||||||
|
mStatusActionView.setTextColor(captionColor);
|
||||||
|
}
|
||||||
|
if (mStatusIconView != null) {
|
||||||
|
mStatusIconView.setColorFilter(captionColor);
|
||||||
|
}
|
||||||
|
|
||||||
mSubjectLabel.setTextColor(getResources().getColor(subjectLabelColorResId, theme));
|
mSubjectLabel.setTextColor(getResources().getColor(subjectLabelColorResId, theme));
|
||||||
mSenderNameTextView.setTextColor(getResources().getColor(timestampColorResId, theme));
|
mSenderNameTextView.setTextColor(captionColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+1
-1
@@ -63,7 +63,7 @@ public class ConversationListActivity extends AbstractConversationListActivity {
|
|||||||
if (actionBar == null) {
|
if (actionBar == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Custom Message+ chrome replaces the ActionBar on the home screen.
|
// Custom Message Q chrome replaces the ActionBar on the home screen.
|
||||||
actionBar.hide();
|
actionBar.hide();
|
||||||
super.updateActionBar(actionBar);
|
super.updateActionBar(actionBar);
|
||||||
}
|
}
|
||||||
|
|||||||
+309
-138
@@ -19,6 +19,7 @@ package com.android.messaging.ui.conversationlist;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
@@ -136,6 +137,7 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
|||||||
private TextView mHomeReadAllButton;
|
private TextView mHomeReadAllButton;
|
||||||
private int mHomeSelectionCount;
|
private int mHomeSelectionCount;
|
||||||
private View mHomeMessagesTop;
|
private View mHomeMessagesTop;
|
||||||
|
private View mHomeCategoryCollapse;
|
||||||
private View mHomeCategoryStrip;
|
private View mHomeCategoryStrip;
|
||||||
private ViewGroup mHomeCategoryAll;
|
private ViewGroup mHomeCategoryAll;
|
||||||
private View mHomeCategoryScroll;
|
private View mHomeCategoryScroll;
|
||||||
@@ -162,6 +164,7 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
|||||||
private int mHomeNavAutomateNaturalWidth;
|
private int mHomeNavAutomateNaturalWidth;
|
||||||
private int mHomeTab = HOME_TAB_MESSAGES;
|
private int mHomeTab = HOME_TAB_MESSAGES;
|
||||||
private View mHomeListProfilePopup;
|
private View mHomeListProfilePopup;
|
||||||
|
private PopupWindow mHomeListProfileWindow;
|
||||||
private View mHomeListProfileScrim;
|
private View mHomeListProfileScrim;
|
||||||
private ImageView mHomeListProfileBlur;
|
private ImageView mHomeListProfileBlur;
|
||||||
private Bitmap mHomeListProfileBlurBitmap;
|
private Bitmap mHomeListProfileBlurBitmap;
|
||||||
@@ -189,12 +192,18 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
|||||||
private static final int HOME_TAB_MESSAGES = 0;
|
private static final int HOME_TAB_MESSAGES = 0;
|
||||||
private static final int HOME_TAB_AUTOMATE = 1;
|
private static final int HOME_TAB_AUTOMATE = 1;
|
||||||
private static final int SEARCH_NEWEST_EDGE_SLOP_DP = 48;
|
private static final int SEARCH_NEWEST_EDGE_SLOP_DP = 48;
|
||||||
private static final int SEARCH_BAR_ANIM_MS = 280;
|
private static final int SEARCH_BAR_ANIM_MS = 220;
|
||||||
private static final int SEARCH_BAR_SLIDE_FALLBACK_DP = 72;
|
private static final int SEARCH_BAR_SLIDE_FALLBACK_DP = 72;
|
||||||
|
private static final int CHROME_SCROLL_THRESHOLD_DP = 10;
|
||||||
|
|
||||||
private Parcelable mListState;
|
private Parcelable mListState;
|
||||||
private View mSearchBar;
|
private View mSearchBar;
|
||||||
|
private View mListTopFade;
|
||||||
|
private View mListBottomFade;
|
||||||
private boolean mSearchBarShown;
|
private boolean mSearchBarShown;
|
||||||
|
private boolean mCategoryStripShown = true;
|
||||||
|
private int mCategoryNaturalHeight;
|
||||||
|
private int mChromeScrollAccum;
|
||||||
|
|
||||||
final Binding<ConversationListData> mListBinding = BindingBase.createBinding(this);
|
final Binding<ConversationListData> mListBinding = BindingBase.createBinding(this);
|
||||||
private final ConversationListItemData mPinnedRowProbe = new ConversationListItemData();
|
private final ConversationListItemData mPinnedRowProbe = new ConversationListItemData();
|
||||||
@@ -259,6 +268,7 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
|||||||
setScrolledToNewestConversationIfNeeded();
|
setScrolledToNewestConversationIfNeeded();
|
||||||
applySearchBarVisibility();
|
applySearchBarVisibility();
|
||||||
resetSearchBarScrollTracking();
|
resetSearchBarScrollTracking();
|
||||||
|
updateListTopFade();
|
||||||
updateHomeMainNavVisibility();
|
updateHomeMainNavVisibility();
|
||||||
refreshPinnedStrip();
|
refreshPinnedStrip();
|
||||||
updateUi();
|
updateUi();
|
||||||
@@ -273,6 +283,50 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isListFlushAtTop() {
|
||||||
|
if (mRecyclerView == null || mAdapter == null || mAdapter.getItemCount() == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
final LinearLayoutManager manager =
|
||||||
|
(LinearLayoutManager) mRecyclerView.getLayoutManager();
|
||||||
|
if (manager == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (manager.findFirstVisibleItemPosition() > 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final View first = manager.findViewByPosition(0);
|
||||||
|
if (first == null) {
|
||||||
|
return !mRecyclerView.canScrollVertically(-1);
|
||||||
|
}
|
||||||
|
return first.getTop() >= mRecyclerView.getPaddingTop() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isListFlushAtBottom() {
|
||||||
|
if (mRecyclerView == null || mAdapter == null || mAdapter.getItemCount() == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return !mRecyclerView.canScrollVertically(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFadeVisible(final View fade, final boolean show) {
|
||||||
|
if (fade == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final int visibility = show ? View.VISIBLE : View.GONE;
|
||||||
|
if (fade.getVisibility() != visibility) {
|
||||||
|
fade.setVisibility(visibility);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateListTopFade() {
|
||||||
|
final boolean listActive = mHomeTab == HOME_TAB_MESSAGES
|
||||||
|
&& !mArchiveMode
|
||||||
|
&& !mForwardMessageMode;
|
||||||
|
setFadeVisible(mListTopFade, listActive && !isListFlushAtTop());
|
||||||
|
setFadeVisible(mListBottomFade, listActive && !isListFlushAtBottom());
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isScrolledToFirstConversation() {
|
private boolean isScrolledToFirstConversation() {
|
||||||
int firstItemPosition = ((LinearLayoutManager) mRecyclerView.getLayoutManager())
|
int firstItemPosition = ((LinearLayoutManager) mRecyclerView.getLayoutManager())
|
||||||
.findFirstCompletelyVisibleItemPosition();
|
.findFirstCompletelyVisibleItemPosition();
|
||||||
@@ -287,6 +341,7 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
|
dismissListProfileWindowImmediate();
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
mListBinding.unbind();
|
mListBinding.unbind();
|
||||||
mHost = null;
|
mHost = null;
|
||||||
@@ -301,6 +356,8 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
|||||||
final ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.conversation_list_fragment,
|
final ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.conversation_list_fragment,
|
||||||
container, false);
|
container, false);
|
||||||
mRecyclerView = rootView.findViewById(android.R.id.list);
|
mRecyclerView = rootView.findViewById(android.R.id.list);
|
||||||
|
mListTopFade = rootView.findViewById(R.id.home_list_top_fade);
|
||||||
|
mListBottomFade = rootView.findViewById(R.id.home_list_bottom_fade);
|
||||||
mEmptyListMessageView = rootView.findViewById(R.id.no_conversations_view);
|
mEmptyListMessageView = rootView.findViewById(R.id.no_conversations_view);
|
||||||
// The default behavior for default layout param generation by LinearLayoutManager is to
|
// The default behavior for default layout param generation by LinearLayoutManager is to
|
||||||
// provide width and height of WRAP_CONTENT, but this is not desirable for
|
// provide width and height of WRAP_CONTENT, but this is not desirable for
|
||||||
@@ -343,6 +400,7 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
|||||||
if (!mArchiveMode && !mForwardMessageMode) {
|
if (!mArchiveMode && !mForwardMessageMode) {
|
||||||
handleSearchBarByScrollOffset(dy);
|
handleSearchBarByScrollOffset(dy);
|
||||||
}
|
}
|
||||||
|
updateListTopFade();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -398,6 +456,7 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
|||||||
}
|
}
|
||||||
setupHomeChrome(rootView);
|
setupHomeChrome(rootView);
|
||||||
mHomeMessagesTop = rootView.findViewById(R.id.home_messages_top);
|
mHomeMessagesTop = rootView.findViewById(R.id.home_messages_top);
|
||||||
|
mHomeCategoryCollapse = rootView.findViewById(R.id.home_category_collapse);
|
||||||
mHomeCategoryStrip = rootView.findViewById(R.id.home_category_strip);
|
mHomeCategoryStrip = rootView.findViewById(R.id.home_category_strip);
|
||||||
mHomeCategoryAll = rootView.findViewById(R.id.home_category_all);
|
mHomeCategoryAll = rootView.findViewById(R.id.home_category_all);
|
||||||
mHomeCategoryScroll = rootView.findViewById(R.id.home_category_scroll);
|
mHomeCategoryScroll = rootView.findViewById(R.id.home_category_scroll);
|
||||||
@@ -588,13 +647,8 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setupSearchBarScroll(final View searchBar) {
|
private void setupSearchBarScroll(final View searchBar) {
|
||||||
searchBar.addOnLayoutChangeListener(
|
|
||||||
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
|
|
||||||
if (bottom - top != oldBottom - oldTop && mSearchBarShown) {
|
|
||||||
updateListPaddingForSearchBar();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
searchBar.post(() -> {
|
searchBar.post(() -> {
|
||||||
|
updateListPaddingForSearchBar();
|
||||||
applySearchBarVisibility();
|
applySearchBarVisibility();
|
||||||
resetSearchBarScrollTracking();
|
resetSearchBarScrollTracking();
|
||||||
});
|
});
|
||||||
@@ -604,15 +658,24 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
|||||||
// No scroll state is kept; visibility is derived from the live scroll position.
|
// No scroll state is kept; visibility is derived from the live scroll position.
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Toward older rows → keep search visible. Toward recent rows → hide. */
|
/** Toward older rows → search. Toward recent rows → categories. */
|
||||||
private void handleSearchBarByScrollOffset(final int dy) {
|
private void handleSearchBarByScrollOffset(final int dy) {
|
||||||
if (mHomeTab != HOME_TAB_MESSAGES || isSelectionMode() || mHomePinnedEditMode
|
if (mHomeTab != HOME_TAB_MESSAGES || isSelectionMode() || mHomePinnedEditMode
|
||||||
|| mSearchBar == null || mRecyclerView == null || dy == 0) {
|
|| mSearchBar == null || mRecyclerView == null || dy == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (dy > 0) {
|
if (isScrolledToFirstConversation() && dy < 0) {
|
||||||
|
mChromeScrollAccum = 0;
|
||||||
|
hideSearchBar();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mChromeScrollAccum += dy;
|
||||||
|
final int threshold = dpToPx(CHROME_SCROLL_THRESHOLD_DP);
|
||||||
|
if (mChromeScrollAccum > threshold) {
|
||||||
|
mChromeScrollAccum = 0;
|
||||||
showSearchBar();
|
showSearchBar();
|
||||||
} else {
|
} else if (mChromeScrollAccum < -threshold) {
|
||||||
|
mChromeScrollAccum = 0;
|
||||||
hideSearchBar();
|
hideSearchBar();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -621,73 +684,122 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
|||||||
if (mSearchBar != null) {
|
if (mSearchBar != null) {
|
||||||
mSearchBar.animate().cancel();
|
mSearchBar.animate().cancel();
|
||||||
}
|
}
|
||||||
|
if (mHomeCategoryStrip != null) {
|
||||||
|
mHomeCategoryStrip.animate().cancel();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private float getSearchBarSlideDistancePx() {
|
private int getChromeSlotHeight() {
|
||||||
if (mSearchBar == null) {
|
int height = 0;
|
||||||
return dpToPx(SEARCH_BAR_SLIDE_FALLBACK_DP);
|
if (mHomeCategoryStrip != null && mHomeCategoryStrip.getHeight() > 0) {
|
||||||
|
height = Math.max(height, mHomeCategoryStrip.getHeight());
|
||||||
|
mCategoryNaturalHeight = mHomeCategoryStrip.getHeight();
|
||||||
}
|
}
|
||||||
final int height = mSearchBar.getHeight();
|
if (mSearchBar != null && mSearchBar.getHeight() > 0) {
|
||||||
return height > 0 ? height : dpToPx(SEARCH_BAR_SLIDE_FALLBACK_DP);
|
height = Math.max(height, mSearchBar.getHeight());
|
||||||
|
}
|
||||||
|
if (height > 0) {
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
if (mCategoryNaturalHeight > 0) {
|
||||||
|
return mCategoryNaturalHeight;
|
||||||
|
}
|
||||||
|
return dpToPx(SEARCH_BAR_SLIDE_FALLBACK_DP);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setChromeInteractive(final View view, final boolean interactive) {
|
||||||
|
if (view == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
view.setClickable(interactive);
|
||||||
|
view.setImportantForAccessibility(interactive
|
||||||
|
? View.IMPORTANT_FOR_ACCESSIBILITY_YES
|
||||||
|
: View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
|
||||||
|
if (view instanceof ViewGroup) {
|
||||||
|
((ViewGroup) view).setDescendantFocusability(interactive
|
||||||
|
? ViewGroup.FOCUS_AFTER_DESCENDANTS
|
||||||
|
: ViewGroup.FOCUS_BLOCK_DESCENDANTS);
|
||||||
|
}
|
||||||
|
view.setOnTouchListener(interactive ? null : (v, event) -> true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyChromeSlot(final boolean searchShown, final boolean animate) {
|
||||||
|
if (mHomeCategoryCollapse == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mHomeTab != HOME_TAB_MESSAGES) {
|
||||||
|
cancelSearchBarAnimation();
|
||||||
|
mSearchBarShown = false;
|
||||||
|
mCategoryStripShown = false;
|
||||||
|
mHomeCategoryCollapse.setVisibility(View.GONE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mHomeCategoryCollapse.setVisibility(View.VISIBLE);
|
||||||
|
mSearchBarShown = searchShown;
|
||||||
|
mCategoryStripShown = !searchShown;
|
||||||
|
if (mSearchBar == null || mHomeCategoryStrip == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final float slot = getChromeSlotHeight();
|
||||||
|
mHomeCategoryCollapse.setMinimumHeight((int) slot);
|
||||||
|
final float searchY = searchShown ? 0f : -slot;
|
||||||
|
cancelSearchBarAnimation();
|
||||||
|
mSearchBar.setAlpha(1f);
|
||||||
|
mHomeCategoryStrip.setAlpha(1f);
|
||||||
|
mHomeCategoryStrip.setTranslationY(0f);
|
||||||
|
if (!animate) {
|
||||||
|
mSearchBar.setTranslationY(searchY);
|
||||||
|
mSearchBar.setVisibility(searchShown ? View.VISIBLE : View.INVISIBLE);
|
||||||
|
mHomeCategoryStrip.setVisibility(searchShown ? View.INVISIBLE : View.VISIBLE);
|
||||||
|
setChromeInteractive(mSearchBar, searchShown);
|
||||||
|
setChromeInteractive(mHomeCategoryStrip, !searchShown);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mHomeCategoryStrip.setVisibility(View.VISIBLE);
|
||||||
|
mSearchBar.setVisibility(View.VISIBLE);
|
||||||
|
setChromeInteractive(mSearchBar, searchShown);
|
||||||
|
setChromeInteractive(mHomeCategoryStrip, !searchShown);
|
||||||
|
mSearchBar.animate()
|
||||||
|
.translationY(searchY)
|
||||||
|
.setDuration(SEARCH_BAR_ANIM_MS)
|
||||||
|
.setInterpolator(UiUtils.DEFAULT_INTERPOLATOR)
|
||||||
|
.withEndAction(() -> {
|
||||||
|
if (mSearchBar == null || mHomeCategoryStrip == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mSearchBarShown) {
|
||||||
|
mHomeCategoryStrip.setVisibility(View.INVISIBLE);
|
||||||
|
} else {
|
||||||
|
mSearchBar.setVisibility(View.INVISIBLE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setCategoryStripShown(final boolean shown, final boolean animate) {
|
||||||
|
applyChromeSlot(!shown, animate);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateListPaddingForSearchBar() {
|
private void updateListPaddingForSearchBar() {
|
||||||
if (mRecyclerView == null) {
|
if (mRecyclerView == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int top = 0;
|
|
||||||
if (mSearchBarShown && mSearchBar != null
|
|
||||||
&& mSearchBar.getVisibility() != View.GONE) {
|
|
||||||
top = mSearchBar.getHeight();
|
|
||||||
if (top == 0) {
|
|
||||||
top = dpToPx(SEARCH_BAR_SLIDE_FALLBACK_DP);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mRecyclerView.setPadding(
|
mRecyclerView.setPadding(
|
||||||
mRecyclerView.getPaddingLeft(),
|
mRecyclerView.getPaddingLeft(),
|
||||||
top,
|
0,
|
||||||
mRecyclerView.getPaddingRight(),
|
mRecyclerView.getPaddingRight(),
|
||||||
mRecyclerView.getPaddingBottom());
|
mRecyclerView.getPaddingBottom());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applySearchBarVisibility() {
|
private void applySearchBarVisibility() {
|
||||||
if (mSearchBar == null || mHomeTab != HOME_TAB_MESSAGES) {
|
if (mHomeTab != HOME_TAB_MESSAGES) {
|
||||||
|
applyChromeSlot(false, false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (mHomePinnedEditMode) {
|
if (mHomePinnedEditMode) {
|
||||||
cancelSearchBarAnimation();
|
applyChromeSlot(false, false);
|
||||||
mSearchBar.setVisibility(View.GONE);
|
|
||||||
mSearchBar.setAlpha(1f);
|
|
||||||
mSearchBar.setTranslationY(0f);
|
|
||||||
updateListPaddingForSearchBar();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
cancelSearchBarAnimation();
|
applyChromeSlot(mSearchBarShown, false);
|
||||||
if (mSearchBarShown) {
|
|
||||||
mSearchBar.setVisibility(View.VISIBLE);
|
|
||||||
mSearchBar.setAlpha(1f);
|
|
||||||
mSearchBar.setTranslationY(0f);
|
|
||||||
updateListPaddingForSearchBar();
|
|
||||||
mSearchBar.post(this::updateListPaddingForSearchBar);
|
|
||||||
} else {
|
|
||||||
mSearchBar.setVisibility(View.GONE);
|
|
||||||
mSearchBar.setAlpha(1f);
|
|
||||||
mSearchBar.setTranslationY(0f);
|
|
||||||
updateListPaddingForSearchBar();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void runSearchBarShowAnimation() {
|
|
||||||
updateListPaddingForSearchBar();
|
|
||||||
final float slide = getSearchBarSlideDistancePx();
|
|
||||||
mSearchBar.setTranslationY(-slide);
|
|
||||||
mSearchBar.setAlpha(0f);
|
|
||||||
mSearchBar.animate()
|
|
||||||
.translationY(0f)
|
|
||||||
.alpha(1f)
|
|
||||||
.setDuration(SEARCH_BAR_ANIM_MS)
|
|
||||||
.setInterpolator(UiUtils.DEFAULT_INTERPOLATOR)
|
|
||||||
.withEndAction(this::updateListPaddingForSearchBar);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showSearchBar() {
|
private void showSearchBar() {
|
||||||
@@ -695,40 +807,14 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
|||||||
|| mSearchBar == null || mSearchBarShown) {
|
|| mSearchBar == null || mSearchBarShown) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mSearchBarShown = true;
|
applyChromeSlot(true, true);
|
||||||
cancelSearchBarAnimation();
|
|
||||||
mSearchBar.setVisibility(View.VISIBLE);
|
|
||||||
updateListPaddingForSearchBar();
|
|
||||||
if (mSearchBar.getHeight() == 0) {
|
|
||||||
mSearchBar.post(() -> {
|
|
||||||
updateListPaddingForSearchBar();
|
|
||||||
runSearchBarShowAnimation();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
runSearchBarShowAnimation();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void hideSearchBar() {
|
private void hideSearchBar() {
|
||||||
if (mSearchBar == null || !mSearchBarShown) {
|
if (mSearchBar == null || !mSearchBarShown) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mSearchBarShown = false;
|
applyChromeSlot(false, true);
|
||||||
cancelSearchBarAnimation();
|
|
||||||
final float slide = getSearchBarSlideDistancePx();
|
|
||||||
mSearchBar.animate()
|
|
||||||
.translationY(-slide)
|
|
||||||
.alpha(0f)
|
|
||||||
.setDuration(SEARCH_BAR_ANIM_MS)
|
|
||||||
.setInterpolator(UiUtils.DEFAULT_INTERPOLATOR)
|
|
||||||
.withEndAction(() -> {
|
|
||||||
if (!mSearchBarShown && mSearchBar != null) {
|
|
||||||
mSearchBar.setVisibility(View.GONE);
|
|
||||||
mSearchBar.setTranslationY(0f);
|
|
||||||
mSearchBar.setAlpha(1f);
|
|
||||||
updateListPaddingForSearchBar();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupHomeChrome(final View rootView) {
|
private void setupHomeChrome(final View rootView) {
|
||||||
@@ -838,7 +924,7 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
|||||||
? mHomeListProfileData.getConversationId() : null;
|
? mHomeListProfileData.getConversationId() : null;
|
||||||
final boolean sameProfile = data.getConversationId() != null
|
final boolean sameProfile = data.getConversationId() != null
|
||||||
&& data.getConversationId().equals(previousProfileId)
|
&& data.getConversationId().equals(previousProfileId)
|
||||||
&& mHomeListProfilePopup.getVisibility() == View.VISIBLE;
|
&& isListProfilePopupShowing();
|
||||||
if (sameProfile) {
|
if (sameProfile) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -874,13 +960,81 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
|||||||
mHomeListProfileContent.setAlpha(0f);
|
mHomeListProfileContent.setAlpha(0f);
|
||||||
mHomeListProfilePopup.setVisibility(View.INVISIBLE);
|
mHomeListProfilePopup.setVisibility(View.INVISIBLE);
|
||||||
bindListProfileBlur();
|
bindListProfileBlur();
|
||||||
|
ensureListProfileWindow();
|
||||||
|
mHomeListProfilePopup.setVisibility(View.VISIBLE);
|
||||||
|
showListProfileWindow();
|
||||||
positionListProfilePopup(anchorView, () -> {
|
positionListProfilePopup(anchorView, () -> {
|
||||||
mHomeListProfilePopup.setVisibility(View.VISIBLE);
|
|
||||||
HomeUiMotion.listProfilePopupEnter(mHomeListProfileScrim, mHomeListProfileContent,
|
HomeUiMotion.listProfilePopupEnter(mHomeListProfileScrim, mHomeListProfileContent,
|
||||||
anchorView);
|
anchorView);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isListProfilePopupShowing() {
|
||||||
|
return mHomeListProfileWindow != null && mHomeListProfileWindow.isShowing();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureListProfileWindow() {
|
||||||
|
if (mHomeListProfilePopup == null || mHomeListProfileWindow != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mHomeListProfilePopup.getParent() instanceof ViewGroup) {
|
||||||
|
((ViewGroup) mHomeListProfilePopup.getParent()).removeView(mHomeListProfilePopup);
|
||||||
|
}
|
||||||
|
mHomeListProfileWindow = new PopupWindow(mHomeListProfilePopup,
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
true);
|
||||||
|
mHomeListProfileWindow.setOutsideTouchable(true);
|
||||||
|
mHomeListProfileWindow.setFocusable(true);
|
||||||
|
mHomeListProfileWindow.setClippingEnabled(false);
|
||||||
|
mHomeListProfileWindow.setElevation(32f);
|
||||||
|
mHomeListProfileWindow.setBackgroundDrawable(
|
||||||
|
new android.graphics.drawable.ColorDrawable(android.graphics.Color.TRANSPARENT));
|
||||||
|
mHomeListProfileWindow.setOnDismissListener(() -> {
|
||||||
|
if (mHomeListProfilePopup != null) {
|
||||||
|
mHomeListProfilePopup.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
recycleListProfileBlur();
|
||||||
|
mHomeListProfileData = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showListProfileWindow() {
|
||||||
|
final Activity activity = getActivity();
|
||||||
|
if (activity == null || mHomeListProfileWindow == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final View decor = activity.getWindow().getDecorView();
|
||||||
|
if (decor.getWidth() > 0 && decor.getHeight() > 0) {
|
||||||
|
mHomeListProfileWindow.setWidth(decor.getWidth());
|
||||||
|
mHomeListProfileWindow.setHeight(decor.getHeight());
|
||||||
|
}
|
||||||
|
if (!mHomeListProfileWindow.isShowing()) {
|
||||||
|
mHomeListProfileWindow.showAtLocation(decor, Gravity.NO_GRAVITY, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dismissListProfileWindowImmediate() {
|
||||||
|
if (mHomeListProfileWindow == null) {
|
||||||
|
recycleListProfileBlur();
|
||||||
|
mHomeListProfileData = null;
|
||||||
|
if (mHomeListProfilePopup != null) {
|
||||||
|
mHomeListProfilePopup.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mHomeListProfileWindow.setOnDismissListener(null);
|
||||||
|
if (mHomeListProfileWindow.isShowing()) {
|
||||||
|
mHomeListProfileWindow.dismiss();
|
||||||
|
}
|
||||||
|
mHomeListProfileWindow = null;
|
||||||
|
if (mHomeListProfilePopup != null) {
|
||||||
|
mHomeListProfilePopup.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
recycleListProfileBlur();
|
||||||
|
mHomeListProfileData = null;
|
||||||
|
}
|
||||||
|
|
||||||
private void updateListProfileActionsPillWidth() {
|
private void updateListProfileActionsPillWidth() {
|
||||||
if (mHomeListProfileActions == null) {
|
if (mHomeListProfileActions == null) {
|
||||||
return;
|
return;
|
||||||
@@ -1003,17 +1157,19 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void dismissHomeListProfilePopup(final boolean animate) {
|
private void dismissHomeListProfilePopup(final boolean animate) {
|
||||||
if (mHomeListProfilePopup == null
|
if (!isListProfilePopupShowing()) {
|
||||||
|| mHomeListProfilePopup.getVisibility() != View.VISIBLE) {
|
|
||||||
recycleListProfileBlur();
|
recycleListProfileBlur();
|
||||||
mHomeListProfileData = null;
|
mHomeListProfileData = null;
|
||||||
|
if (mHomeListProfilePopup != null) {
|
||||||
|
mHomeListProfilePopup.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final Runnable finish = () -> {
|
final Runnable finish = () -> {
|
||||||
mHomeListProfilePopup.setVisibility(View.GONE);
|
|
||||||
resetListProfilePopupPosition();
|
resetListProfilePopupPosition();
|
||||||
recycleListProfileBlur();
|
if (mHomeListProfileWindow != null && mHomeListProfileWindow.isShowing()) {
|
||||||
mHomeListProfileData = null;
|
mHomeListProfileWindow.dismiss();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
if (animate) {
|
if (animate) {
|
||||||
HomeUiMotion.listProfilePopupExit(mHomeListProfileScrim, mHomeListProfileContent,
|
HomeUiMotion.listProfilePopupExit(mHomeListProfileScrim, mHomeListProfileContent,
|
||||||
@@ -1079,14 +1235,7 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
|||||||
}
|
}
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
setPinnedEditMode(false, false);
|
setPinnedEditMode(false, false);
|
||||||
mSearchBarShown = false;
|
applyChromeSlot(false, false);
|
||||||
cancelSearchBarAnimation();
|
|
||||||
if (mSearchBar != null) {
|
|
||||||
mSearchBar.setVisibility(View.GONE);
|
|
||||||
mSearchBar.setAlpha(1f);
|
|
||||||
mSearchBar.setTranslationY(0f);
|
|
||||||
}
|
|
||||||
updateListPaddingForSearchBar();
|
|
||||||
}
|
}
|
||||||
updateHomeSelectionBar(0);
|
updateHomeSelectionBar(0);
|
||||||
updateHomeComposeButtonVisibility();
|
updateHomeComposeButtonVisibility();
|
||||||
@@ -1129,10 +1278,10 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
|||||||
if (mHomeMessagesTop != null) {
|
if (mHomeMessagesTop != null) {
|
||||||
mHomeMessagesTop.setVisibility(View.VISIBLE);
|
mHomeMessagesTop.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
if (mHomeCategoryStrip != null) {
|
if (showMessages) {
|
||||||
mHomeCategoryStrip.setVisibility(showMessages ? View.VISIBLE : View.GONE);
|
setCategoryStripShown(!mSearchBarShown, false);
|
||||||
} else if (mHomeCategoryScroll != null) {
|
} else {
|
||||||
mHomeCategoryScroll.setVisibility(showMessages ? View.VISIBLE : View.GONE);
|
setCategoryStripShown(false, false);
|
||||||
}
|
}
|
||||||
final CharSequence title = getString(showMessages
|
final CharSequence title = getString(showMessages
|
||||||
? R.string.home_title : R.string.home_automation_title);
|
? R.string.home_title : R.string.home_automation_title);
|
||||||
@@ -1156,14 +1305,7 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!showMessages) {
|
if (!showMessages) {
|
||||||
mSearchBarShown = false;
|
applyChromeSlot(false, false);
|
||||||
cancelSearchBarAnimation();
|
|
||||||
if (mSearchBar != null) {
|
|
||||||
mSearchBar.setVisibility(View.GONE);
|
|
||||||
mSearchBar.setAlpha(1f);
|
|
||||||
mSearchBar.setTranslationY(0f);
|
|
||||||
}
|
|
||||||
updateListPaddingForSearchBar();
|
|
||||||
if (mEmptyListMessageView != null) {
|
if (mEmptyListMessageView != null) {
|
||||||
mEmptyListMessageView.setVisibility(View.GONE);
|
mEmptyListMessageView.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
@@ -1175,10 +1317,15 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
|||||||
updateHomeNavHighlight(animate);
|
updateHomeNavHighlight(animate);
|
||||||
updateHomeMainNavVisibility();
|
updateHomeMainNavVisibility();
|
||||||
refreshPinnedStrip();
|
refreshPinnedStrip();
|
||||||
|
updateListTopFade();
|
||||||
mHomeTabReady = true;
|
mHomeTabReady = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean onHomeBackPressed() {
|
public boolean onHomeBackPressed() {
|
||||||
|
if (isListProfilePopupShowing()) {
|
||||||
|
dismissHomeListProfilePopup(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (mHomeTab == HOME_TAB_AUTOMATE) {
|
if (mHomeTab == HOME_TAB_AUTOMATE) {
|
||||||
selectHomeTab(HOME_TAB_MESSAGES);
|
selectHomeTab(HOME_TAB_MESSAGES);
|
||||||
return true;
|
return true;
|
||||||
@@ -1415,12 +1562,15 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mHomeListProfileBlurBitmap = captureDecorSnapshot();
|
mHomeListProfileBlurBitmap = captureDecorSnapshot();
|
||||||
|
if (mHomeListProfileBlurBitmap == null) {
|
||||||
|
mHomeListProfileBlurBitmap = captureViewSnapshot(getView());
|
||||||
|
}
|
||||||
if (mHomeListProfileBlurBitmap == null) {
|
if (mHomeListProfileBlurBitmap == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mHomeListProfileBlur.setImageBitmap(mHomeListProfileBlurBitmap);
|
mHomeListProfileBlur.setImageBitmap(mHomeListProfileBlurBitmap);
|
||||||
mHomeListProfileBlur.setRenderEffect(RenderEffect.createBlurEffect(
|
mHomeListProfileBlur.setRenderEffect(RenderEffect.createBlurEffect(
|
||||||
36f, 36f, Shader.TileMode.CLAMP));
|
64f, 64f, Shader.TileMode.CLAMP));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void recycleListProfileBlur() {
|
private void recycleListProfileBlur() {
|
||||||
@@ -1700,8 +1850,11 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
|||||||
if (activity == null) {
|
if (activity == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
final View root = activity.getWindow().getDecorView();
|
return captureViewSnapshot(activity.getWindow().getDecorView());
|
||||||
if (root.getWidth() <= 0 || root.getHeight() <= 0) {
|
}
|
||||||
|
|
||||||
|
private Bitmap captureViewSnapshot(final View root) {
|
||||||
|
if (root == null || root.getWidth() <= 0 || root.getHeight() <= 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
final Bitmap bitmap = Bitmap.createBitmap(
|
final Bitmap bitmap = Bitmap.createBitmap(
|
||||||
@@ -1787,24 +1940,26 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
|||||||
mHomePopover = null;
|
mHomePopover = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
final int xOff;
|
final int endInset = getResources().getDimensionPixelSize(
|
||||||
if (alignEnd) {
|
R.dimen.looply_screen_padding);
|
||||||
final int endInset = getResources().getDimensionPixelSize(
|
final int[] anchorLoc = new int[2];
|
||||||
R.dimen.looply_screen_padding);
|
anchor.getLocationOnScreen(anchorLoc);
|
||||||
final int[] anchorLoc = new int[2];
|
final View root = getView();
|
||||||
anchor.getLocationOnScreen(anchorLoc);
|
final int windowRight;
|
||||||
final View root = getView();
|
if (root != null) {
|
||||||
final int windowRight;
|
final int[] rootLoc = new int[2];
|
||||||
if (root != null) {
|
root.getLocationOnScreen(rootLoc);
|
||||||
final int[] rootLoc = new int[2];
|
windowRight = rootLoc[0] + root.getWidth();
|
||||||
root.getLocationOnScreen(rootLoc);
|
|
||||||
windowRight = rootLoc[0] + root.getWidth();
|
|
||||||
} else {
|
|
||||||
windowRight = getResources().getDisplayMetrics().widthPixels;
|
|
||||||
}
|
|
||||||
xOff = windowRight - endInset - content.getMeasuredWidth() - anchorLoc[0];
|
|
||||||
} else {
|
} else {
|
||||||
xOff = 0;
|
windowRight = getResources().getDisplayMetrics().widthPixels;
|
||||||
|
}
|
||||||
|
int xOff = alignEnd
|
||||||
|
? windowRight - endInset - content.getMeasuredWidth() - anchorLoc[0]
|
||||||
|
: 0;
|
||||||
|
final int overflow = (anchorLoc[0] + xOff + content.getMeasuredWidth())
|
||||||
|
- (windowRight - endInset);
|
||||||
|
if (overflow > 0) {
|
||||||
|
xOff -= overflow;
|
||||||
}
|
}
|
||||||
popup.showAsDropDown(anchor, xOff, 8);
|
popup.showAsDropDown(anchor, xOff, 8);
|
||||||
HomeUiMotion.popoverEnter(content, alignEnd);
|
HomeUiMotion.popoverEnter(content, alignEnd);
|
||||||
@@ -1840,6 +1995,10 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
|||||||
selectHomeChip(ConversationListAdapter.FILTER_ALL);
|
selectHomeChip(ConversationListAdapter.FILTER_ALL);
|
||||||
dismissHomePopover();
|
dismissHomePopover();
|
||||||
});
|
});
|
||||||
|
content.findViewById(R.id.popover_share_app).setOnClickListener(v -> {
|
||||||
|
dismissHomePopover();
|
||||||
|
shareThisApp();
|
||||||
|
});
|
||||||
content.findViewById(R.id.popover_settings).setOnClickListener(v -> {
|
content.findViewById(R.id.popover_settings).setOnClickListener(v -> {
|
||||||
dismissHomePopover();
|
dismissHomePopover();
|
||||||
launchHomeSubScreen(() -> {
|
launchHomeSubScreen(() -> {
|
||||||
@@ -1850,8 +2009,21 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void shareThisApp() {
|
||||||
|
final Context context = getContext();
|
||||||
|
if (context == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final String storeUrl = "https://play.google.com/store/apps/details?id="
|
||||||
|
+ context.getPackageName();
|
||||||
|
final Intent share = new Intent(Intent.ACTION_SEND);
|
||||||
|
share.setType("text/plain");
|
||||||
|
share.putExtra(Intent.EXTRA_TEXT, getString(R.string.home_share_app_text, storeUrl));
|
||||||
|
startActivity(Intent.createChooser(share, getString(R.string.home_popover_share_app)));
|
||||||
|
}
|
||||||
|
|
||||||
private void showHomeEditPopover(final View anchor) {
|
private void showHomeEditPopover(final View anchor) {
|
||||||
final PopupWindow popup = showHomePopover(anchor, R.layout.home_popover_edit, false);
|
final PopupWindow popup = showHomePopover(anchor, R.layout.home_popover_edit, true);
|
||||||
final View content = popup.getContentView();
|
final View content = popup.getContentView();
|
||||||
content.findViewById(R.id.popover_select_messages).setOnClickListener(v -> {
|
content.findViewById(R.id.popover_select_messages).setOnClickListener(v -> {
|
||||||
dismissHomePopoverImmediate();
|
dismissHomePopoverImmediate();
|
||||||
@@ -2107,6 +2279,7 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
|||||||
@Override
|
@Override
|
||||||
public void onPause() {
|
public void onPause() {
|
||||||
super.onPause();
|
super.onPause();
|
||||||
|
dismissHomeListProfilePopup(false);
|
||||||
dismissHomePopover();
|
dismissHomePopover();
|
||||||
if (mHomeBottomNav != null && !mArchiveMode && !mForwardMessageMode) {
|
if (mHomeBottomNav != null && !mArchiveMode && !mForwardMessageMode) {
|
||||||
mHomeBottomNav.setVisibility(View.GONE);
|
mHomeBottomNav.setVisibility(View.GONE);
|
||||||
@@ -2139,6 +2312,9 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
|||||||
|
|
||||||
public void updateUi() {
|
public void updateUi() {
|
||||||
mAdapter.notifyDataSetChanged();
|
mAdapter.notifyDataSetChanged();
|
||||||
|
if (mRecyclerView != null) {
|
||||||
|
mRecyclerView.post(this::updateListTopFade);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -2382,13 +2558,8 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
|||||||
if (mSearchBar == null) {
|
if (mSearchBar == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
cancelSearchBarAnimation();
|
|
||||||
if (mHomePinnedEditMode) {
|
if (mHomePinnedEditMode) {
|
||||||
mSearchBarShown = false;
|
applyChromeSlot(false, false);
|
||||||
mSearchBar.setVisibility(View.GONE);
|
|
||||||
mSearchBar.setAlpha(1f);
|
|
||||||
mSearchBar.setTranslationY(0f);
|
|
||||||
updateListPaddingForSearchBar();
|
|
||||||
} else {
|
} else {
|
||||||
applySearchBarVisibility();
|
applySearchBarVisibility();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,294 @@
|
|||||||
|
package com.android.messaging.ui.home;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.Application;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.lifecycle.Lifecycle;
|
||||||
|
import androidx.lifecycle.LifecycleEventObserver;
|
||||||
|
import androidx.lifecycle.ProcessLifecycleOwner;
|
||||||
|
|
||||||
|
import com.android.messaging.util.BuglePrefs;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/** App lock credentials, session, and process-level unlock gate (File Plus behavior). */
|
||||||
|
public final class HomeAppLock {
|
||||||
|
|
||||||
|
public enum Method {
|
||||||
|
NONE, PIN, PATTERN, PASSWORD;
|
||||||
|
|
||||||
|
static Method fromStored(@Nullable final String value) {
|
||||||
|
if (TextUtils.isEmpty(value)) {
|
||||||
|
return NONE;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Method.valueOf(value);
|
||||||
|
} catch (final IllegalArgumentException ignored) {
|
||||||
|
return NONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String EXTRA_MODE = "home_app_lock_mode";
|
||||||
|
public static final int MODE_UNLOCK = 0;
|
||||||
|
public static final int MODE_SET_PIN = 1;
|
||||||
|
public static final int MODE_SET_PATTERN = 2;
|
||||||
|
public static final int MODE_SET_PASSWORD = 3;
|
||||||
|
|
||||||
|
private static final String KEY_ENABLED = "home_app_lock_enabled";
|
||||||
|
private static final String KEY_METHOD = "home_app_lock_method";
|
||||||
|
private static final String KEY_HASH = "home_app_lock_hash";
|
||||||
|
private static final String HASH_SALT = "Messaging.Security.v1";
|
||||||
|
|
||||||
|
private static boolean sUnlocked;
|
||||||
|
private static boolean sShowingUnlock;
|
||||||
|
|
||||||
|
private HomeAppLock() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void install(@NonNull final Application application) {
|
||||||
|
ProcessLifecycleOwner.get().getLifecycle().addObserver(
|
||||||
|
(LifecycleEventObserver) (source, event) -> {
|
||||||
|
if (event == Lifecycle.Event.ON_STOP && requiresUnlock()) {
|
||||||
|
sUnlocked = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(@NonNull final Activity activity,
|
||||||
|
@Nullable final Bundle savedInstanceState) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityStarted(@NonNull final Activity activity) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityResumed(@NonNull final Activity activity) {
|
||||||
|
maybeLock(activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityPaused(@NonNull final Activity activity) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityStopped(@NonNull final Activity activity) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivitySaveInstanceState(@NonNull final Activity activity,
|
||||||
|
@NonNull final Bundle outState) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityDestroyed(@NonNull final Activity activity) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isEnabled() {
|
||||||
|
return prefs().getBoolean(KEY_ENABLED, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setEnabled(final boolean enabled) {
|
||||||
|
prefs().putBoolean(KEY_ENABLED, enabled);
|
||||||
|
if (enabled) {
|
||||||
|
markUnlocked();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Method getMethod() {
|
||||||
|
return Method.fromStored(prefs().getString(KEY_METHOD, Method.NONE.name()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean hasLockConfigured() {
|
||||||
|
return getMethod() != Method.NONE && !TextUtils.isEmpty(prefs().getString(KEY_HASH, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean requiresUnlock() {
|
||||||
|
return isEnabled() && hasLockConfigured();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setPin(@NonNull final String pin) {
|
||||||
|
if (pin.length() != 6 || !pin.matches("\\d{6}")) {
|
||||||
|
throw new IllegalArgumentException("PIN must be 6 digits");
|
||||||
|
}
|
||||||
|
store(Method.PIN, pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setPattern(@NonNull final List<Integer> cells) {
|
||||||
|
if (cells.size() < 4) {
|
||||||
|
throw new IllegalArgumentException("Pattern too short");
|
||||||
|
}
|
||||||
|
store(Method.PATTERN, joinCells(cells));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setPassword(@NonNull final String password) {
|
||||||
|
if (password.length() < 8) {
|
||||||
|
throw new IllegalArgumentException("Password too short");
|
||||||
|
}
|
||||||
|
store(Method.PASSWORD, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean verifyPin(@NonNull final String pin) {
|
||||||
|
return verify(Method.PIN, pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean verifyPattern(@NonNull final List<Integer> cells) {
|
||||||
|
return verify(Method.PATTERN, joinCells(cells));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean verifyPassword(@NonNull final String password) {
|
||||||
|
return verify(Method.PASSWORD, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isUnlockedThisSession() {
|
||||||
|
return sUnlocked;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void markUnlocked() {
|
||||||
|
sUnlocked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clearUnlocked() {
|
||||||
|
sUnlocked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void markUnlockShowing(final boolean showing) {
|
||||||
|
sShowingUnlock = showing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Intent unlockIntent(@NonNull final Activity activity) {
|
||||||
|
return createIntent(activity, MODE_UNLOCK);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Intent createIntent(@NonNull final Activity activity, final int mode) {
|
||||||
|
final Intent intent = new Intent(activity, HomeAppLockActivity.class);
|
||||||
|
intent.putExtra(EXTRA_MODE, mode);
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<Integer> appendPatternCells(@NonNull final List<Integer> current, final int next) {
|
||||||
|
if (current.isEmpty()) {
|
||||||
|
final List<Integer> start = new ArrayList<>();
|
||||||
|
start.add(next);
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
final int last = current.get(current.size() - 1);
|
||||||
|
if (next == last) {
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
final Integer bridge = bridgedCellIndex(last, next);
|
||||||
|
final List<Integer> out = new ArrayList<>(current);
|
||||||
|
if (bridge != null && !current.contains(bridge)) {
|
||||||
|
out.add(bridge);
|
||||||
|
}
|
||||||
|
out.add(next);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
static Integer bridgedCellIndex(final int from, final int to) {
|
||||||
|
final int fromRow = from / 3;
|
||||||
|
final int fromCol = from % 3;
|
||||||
|
final int toRow = to / 3;
|
||||||
|
final int toCol = to % 3;
|
||||||
|
final int dRow = toRow - fromRow;
|
||||||
|
final int dCol = toCol - fromCol;
|
||||||
|
if (dRow == 0 && dCol == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (dRow != 0 && dCol != 0 && Math.abs(dRow) != Math.abs(dCol)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (dRow != 0 && dRow % 2 != 0 && Math.abs(dRow) > 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (dCol != 0 && dCol % 2 != 0 && Math.abs(dCol) > 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final int midRow = fromRow + dRow / 2;
|
||||||
|
final int midCol = fromCol + dCol / 2;
|
||||||
|
if (midRow == fromRow && midCol == fromCol) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (midRow == toRow && midCol == toCol) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return midRow * 3 + midCol;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void maybeLock(@NonNull final Activity activity) {
|
||||||
|
if (!requiresUnlock() || sUnlocked || sShowingUnlock) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (activity instanceof HomeAppLockActivity) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
activity.getWindow().getDecorView().post(() -> {
|
||||||
|
if (activity.isFinishing() || activity.isDestroyed()
|
||||||
|
|| !requiresUnlock() || sUnlocked || sShowingUnlock) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sShowingUnlock = true;
|
||||||
|
activity.startActivity(unlockIntent(activity));
|
||||||
|
activity.overridePendingTransition(0, 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void store(@NonNull final Method method, @NonNull final String secret) {
|
||||||
|
final BuglePrefs prefs = prefs();
|
||||||
|
prefs.putString(KEY_HASH, hash(secret));
|
||||||
|
prefs.putString(KEY_METHOD, method.name());
|
||||||
|
prefs.putBoolean(KEY_ENABLED, true);
|
||||||
|
markUnlocked();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean verify(@NonNull final Method method, @NonNull final String secret) {
|
||||||
|
if (getMethod() != method) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final String stored = prefs().getString(KEY_HASH, null);
|
||||||
|
return !TextUtils.isEmpty(stored) && stored.equals(hash(secret));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String joinCells(@NonNull final List<Integer> cells) {
|
||||||
|
final StringBuilder builder = new StringBuilder();
|
||||||
|
for (int i = 0; i < cells.size(); i++) {
|
||||||
|
if (i > 0) {
|
||||||
|
builder.append(',');
|
||||||
|
}
|
||||||
|
builder.append(cells.get(i));
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String hash(@NonNull final String value) {
|
||||||
|
try {
|
||||||
|
final MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||||
|
final byte[] bytes = digest.digest(
|
||||||
|
(HASH_SALT + "|" + value).getBytes(StandardCharsets.UTF_8));
|
||||||
|
final StringBuilder hex = new StringBuilder(bytes.length * 2);
|
||||||
|
for (final byte b : bytes) {
|
||||||
|
hex.append(String.format(Locale.US, "%02x", b));
|
||||||
|
}
|
||||||
|
return hex.toString();
|
||||||
|
} catch (final Exception e) {
|
||||||
|
throw new IllegalStateException("Unable to hash lock secret", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BuglePrefs prefs() {
|
||||||
|
return BuglePrefs.getApplicationPrefs();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,483 @@
|
|||||||
|
package com.android.messaging.ui.home;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.Editable;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.text.TextWatcher;
|
||||||
|
import android.text.method.PasswordTransformationMethod;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.inputmethod.EditorInfo;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.activity.OnBackPressedCallback;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
|
import com.android.messaging.R;
|
||||||
|
import com.android.messaging.ui.BugleActionBarActivity;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/** Unlock overlay and PIN / pattern / password setup screens. */
|
||||||
|
public class HomeAppLockActivity extends BugleActionBarActivity {
|
||||||
|
|
||||||
|
private int mMode;
|
||||||
|
private int mStep;
|
||||||
|
private String mFirstSecret = "";
|
||||||
|
private final List<Integer> mFirstPattern = new ArrayList<>();
|
||||||
|
private String mPin = "";
|
||||||
|
private TextView mSubtitle;
|
||||||
|
private TextView mError;
|
||||||
|
private ViewGroup mPinDots;
|
||||||
|
private HomePatternLockView mPatternView;
|
||||||
|
private View mPatternRecorded;
|
||||||
|
private EditText mPassword;
|
||||||
|
private ImageView mPasswordToggle;
|
||||||
|
private View mPasswordRules;
|
||||||
|
private TextView mPasswordLabel;
|
||||||
|
private boolean mPasswordVisible;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
if (getSupportActionBar() != null) {
|
||||||
|
getSupportActionBar().hide();
|
||||||
|
}
|
||||||
|
mMode = getIntent().getIntExtra(HomeAppLock.EXTRA_MODE, HomeAppLock.MODE_UNLOCK);
|
||||||
|
if (mMode == HomeAppLock.MODE_UNLOCK) {
|
||||||
|
HomeAppLock.markUnlockShowing(true);
|
||||||
|
if (!HomeAppLock.requiresUnlock()) {
|
||||||
|
HomeAppLock.markUnlocked();
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setContentView(R.layout.home_lock_unlock);
|
||||||
|
} else {
|
||||||
|
setContentView(R.layout.home_lock_setup);
|
||||||
|
HomeSubHeader.bind(this, headerTitle(), v -> finish());
|
||||||
|
}
|
||||||
|
mSubtitle = findViewById(R.id.home_lock_subtitle);
|
||||||
|
mError = findViewById(R.id.home_lock_error);
|
||||||
|
final FrameLayout body = findViewById(R.id.home_lock_body);
|
||||||
|
final LayoutInflater inflater = LayoutInflater.from(this);
|
||||||
|
if (isPinMode()) {
|
||||||
|
inflater.inflate(R.layout.home_lock_pin_body, body, true);
|
||||||
|
bindPinPad(body);
|
||||||
|
} else if (isPatternMode()) {
|
||||||
|
inflater.inflate(R.layout.home_lock_pattern_body, body, true);
|
||||||
|
bindPattern(body);
|
||||||
|
} else {
|
||||||
|
inflater.inflate(R.layout.home_lock_password_body, body, true);
|
||||||
|
bindPassword(body);
|
||||||
|
}
|
||||||
|
refreshCopy();
|
||||||
|
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
|
||||||
|
@Override
|
||||||
|
public void handleOnBackPressed() {
|
||||||
|
if (mMode == HomeAppLock.MODE_UNLOCK) {
|
||||||
|
moveTaskToBack(true);
|
||||||
|
} else {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
if (mMode == HomeAppLock.MODE_UNLOCK && isFinishing()) {
|
||||||
|
HomeAppLock.markUnlockShowing(false);
|
||||||
|
}
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void finish() {
|
||||||
|
super.finish();
|
||||||
|
if (mMode != HomeAppLock.MODE_UNLOCK) {
|
||||||
|
overridePendingTransition(R.anim.home_activity_hold, R.anim.home_activity_close);
|
||||||
|
} else {
|
||||||
|
overridePendingTransition(0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bindPinPad(final View host) {
|
||||||
|
mPinDots = host.findViewById(R.id.home_lock_pin_dots);
|
||||||
|
final ViewGroup keypad = host.findViewById(R.id.home_lock_pin_keypad);
|
||||||
|
bindDigitKeys(keypad);
|
||||||
|
final View backspace = host.findViewById(R.id.home_lock_pin_backspace);
|
||||||
|
if (backspace != null) {
|
||||||
|
backspace.setOnClickListener(v -> {
|
||||||
|
if (!mPin.isEmpty()) {
|
||||||
|
mPin = mPin.substring(0, mPin.length() - 1);
|
||||||
|
updatePinDots();
|
||||||
|
hideError();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
updatePinDots();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bindDigitKeys(final ViewGroup parent) {
|
||||||
|
for (int i = 0; i < parent.getChildCount(); i++) {
|
||||||
|
final View child = parent.getChildAt(i);
|
||||||
|
if (child instanceof ViewGroup) {
|
||||||
|
bindDigitKeys((ViewGroup) child);
|
||||||
|
} else if (child instanceof TextView) {
|
||||||
|
final String text = ((TextView) child).getText().toString();
|
||||||
|
if (text.length() == 1 && Character.isDigit(text.charAt(0))) {
|
||||||
|
child.setOnClickListener(v -> appendPin(text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendPin(final String digit) {
|
||||||
|
if (mPin.length() >= 6) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mPin += digit;
|
||||||
|
updatePinDots();
|
||||||
|
hideError();
|
||||||
|
if (mPin.length() < 6) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mMode == HomeAppLock.MODE_UNLOCK) {
|
||||||
|
if (HomeAppLock.verifyPin(mPin)) {
|
||||||
|
unlockAndFinish();
|
||||||
|
} else {
|
||||||
|
showError(R.string.home_lock_incorrect_pin);
|
||||||
|
mPin = "";
|
||||||
|
updatePinDots();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mStep == 0) {
|
||||||
|
mFirstSecret = mPin;
|
||||||
|
mPin = "";
|
||||||
|
mStep = 1;
|
||||||
|
updatePinDots();
|
||||||
|
refreshCopy();
|
||||||
|
} else if (mFirstSecret.equals(mPin)) {
|
||||||
|
HomeAppLock.setPin(mPin);
|
||||||
|
finish();
|
||||||
|
} else {
|
||||||
|
showMismatch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updatePinDots() {
|
||||||
|
if (mPinDots == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < mPinDots.getChildCount(); i++) {
|
||||||
|
mPinDots.getChildAt(i).setSelected(i < mPin.length());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bindPattern(final View host) {
|
||||||
|
mPatternView = host.findViewById(R.id.home_lock_pattern);
|
||||||
|
mPatternRecorded = host.findViewById(R.id.home_lock_pattern_recorded);
|
||||||
|
host.findViewById(R.id.home_lock_pattern_clear).setOnClickListener(v -> {
|
||||||
|
if (mPatternView != null) {
|
||||||
|
mPatternView.clear();
|
||||||
|
}
|
||||||
|
if (mPatternRecorded != null) {
|
||||||
|
mPatternRecorded.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
hideError();
|
||||||
|
});
|
||||||
|
mPatternView.setListener(new HomePatternLockView.Listener() {
|
||||||
|
@Override
|
||||||
|
public void onPatternChanged(final List<Integer> cells) {
|
||||||
|
hideError();
|
||||||
|
if (mPatternRecorded != null) {
|
||||||
|
mPatternRecorded.setVisibility(
|
||||||
|
cells.size() >= 4 ? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPatternComplete(final List<Integer> cells) {
|
||||||
|
if (mMode == HomeAppLock.MODE_UNLOCK) {
|
||||||
|
if (HomeAppLock.verifyPattern(cells)) {
|
||||||
|
unlockAndFinish();
|
||||||
|
} else {
|
||||||
|
showError(R.string.home_lock_incorrect_pattern);
|
||||||
|
mPatternView.clear();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (cells.size() < 4) {
|
||||||
|
mPatternView.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mStep == 0) {
|
||||||
|
mFirstPattern.clear();
|
||||||
|
mFirstPattern.addAll(cells);
|
||||||
|
mPatternView.clear();
|
||||||
|
mStep = 1;
|
||||||
|
if (mPatternRecorded != null) {
|
||||||
|
mPatternRecorded.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
refreshCopy();
|
||||||
|
} else if (mFirstPattern.equals(cells)) {
|
||||||
|
HomeAppLock.setPattern(cells);
|
||||||
|
finish();
|
||||||
|
} else {
|
||||||
|
showMismatch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bindPassword(final View host) {
|
||||||
|
mPassword = host.findViewById(R.id.home_lock_password);
|
||||||
|
mPasswordToggle = host.findViewById(R.id.home_lock_password_toggle);
|
||||||
|
mPasswordRules = host.findViewById(R.id.home_lock_password_rules);
|
||||||
|
mPasswordLabel = host.findViewById(R.id.home_lock_password_label);
|
||||||
|
if (mMode != HomeAppLock.MODE_UNLOCK) {
|
||||||
|
mPasswordRules.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
mPasswordToggle.setOnClickListener(v -> {
|
||||||
|
mPasswordVisible = !mPasswordVisible;
|
||||||
|
final int start = mPassword.getSelectionStart();
|
||||||
|
mPassword.setTransformationMethod(mPasswordVisible
|
||||||
|
? null : PasswordTransformationMethod.getInstance());
|
||||||
|
mPasswordToggle.setImageResource(mPasswordVisible
|
||||||
|
? R.drawable.ic_home_visibility_off : R.drawable.ic_home_visibility);
|
||||||
|
if (start >= 0) {
|
||||||
|
mPassword.setSelection(start);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mPassword.setOnEditorActionListener((v, actionId, event) -> {
|
||||||
|
if (actionId == EditorInfo.IME_ACTION_DONE
|
||||||
|
|| (event != null && event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) {
|
||||||
|
attemptPassword();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
if (mMode != HomeAppLock.MODE_UNLOCK) {
|
||||||
|
mPassword.requestFocus();
|
||||||
|
}
|
||||||
|
mPassword.addTextChangedListener(new TextWatcher() {
|
||||||
|
@Override
|
||||||
|
public void beforeTextChanged(final CharSequence s, final int start, final int count,
|
||||||
|
final int after) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(final CharSequence s, final int start, final int before,
|
||||||
|
final int count) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterTextChanged(final Editable s) {
|
||||||
|
hideError();
|
||||||
|
final String value = s.toString();
|
||||||
|
if (mMode == HomeAppLock.MODE_UNLOCK) {
|
||||||
|
if (value.length() >= 8 && HomeAppLock.verifyPassword(value)) {
|
||||||
|
unlockAndFinish();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mStep == 0) {
|
||||||
|
updatePasswordRules(value);
|
||||||
|
if (isPasswordValid(value)) {
|
||||||
|
mFirstSecret = value;
|
||||||
|
mStep = 1;
|
||||||
|
mPassword.setText("");
|
||||||
|
refreshCopy();
|
||||||
|
}
|
||||||
|
} else if (value.length() >= mFirstSecret.length()) {
|
||||||
|
if (mFirstSecret.equals(value)) {
|
||||||
|
HomeAppLock.setPassword(value);
|
||||||
|
finish();
|
||||||
|
} else {
|
||||||
|
showMismatch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void attemptPassword() {
|
||||||
|
final String value = mPassword.getText() == null ? "" : mPassword.getText().toString();
|
||||||
|
if (HomeAppLock.verifyPassword(value)) {
|
||||||
|
unlockAndFinish();
|
||||||
|
} else if (!TextUtils.isEmpty(value)) {
|
||||||
|
showError(R.string.home_lock_incorrect_password);
|
||||||
|
mPassword.setText("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updatePasswordRules(final String password) {
|
||||||
|
setRule(R.id.home_lock_rule_length, password.length() >= 8);
|
||||||
|
setRule(R.id.home_lock_rule_number, containsDigit(password));
|
||||||
|
setRule(R.id.home_lock_rule_symbol, containsSymbol(password));
|
||||||
|
setRule(R.id.home_lock_rule_upper, containsUpper(password));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setRule(final int id, final boolean ok) {
|
||||||
|
final TextView view = findViewById(id);
|
||||||
|
if (view == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
view.setTextColor(getColor(ok ? R.color.home_text_primary : R.color.home_text_secondary));
|
||||||
|
view.setCompoundDrawableTintList(ContextCompat.getColorStateList(this,
|
||||||
|
ok ? R.color.home_accent : R.color.home_text_tertiary));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isPasswordValid(final String password) {
|
||||||
|
return password.length() >= 8 && containsDigit(password)
|
||||||
|
&& containsSymbol(password) && containsUpper(password);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean containsDigit(final String value) {
|
||||||
|
for (int i = 0; i < value.length(); i++) {
|
||||||
|
if (Character.isDigit(value.charAt(i))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean containsSymbol(final String value) {
|
||||||
|
for (int i = 0; i < value.length(); i++) {
|
||||||
|
if (!Character.isLetterOrDigit(value.charAt(i))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean containsUpper(final String value) {
|
||||||
|
for (int i = 0; i < value.length(); i++) {
|
||||||
|
if (Character.isUpperCase(value.charAt(i))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshCopy() {
|
||||||
|
if (mSubtitle != null) {
|
||||||
|
mSubtitle.setText(subtitle());
|
||||||
|
}
|
||||||
|
if (mPasswordLabel != null) {
|
||||||
|
mPasswordLabel.setText(mStep == 0
|
||||||
|
? R.string.home_lock_password_hint
|
||||||
|
: R.string.home_lock_password_confirm_hint);
|
||||||
|
}
|
||||||
|
if (mPasswordRules != null) {
|
||||||
|
mPasswordRules.setVisibility(mMode != HomeAppLock.MODE_UNLOCK && mStep == 0
|
||||||
|
? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int headerTitle() {
|
||||||
|
switch (mMode) {
|
||||||
|
case HomeAppLock.MODE_SET_PIN:
|
||||||
|
return R.string.home_lock_set_pin;
|
||||||
|
case HomeAppLock.MODE_SET_PATTERN:
|
||||||
|
return R.string.home_lock_set_pattern;
|
||||||
|
case HomeAppLock.MODE_SET_PASSWORD:
|
||||||
|
return R.string.home_lock_set_password;
|
||||||
|
default:
|
||||||
|
return R.string.home_title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int subtitle() {
|
||||||
|
if (mMode == HomeAppLock.MODE_UNLOCK) {
|
||||||
|
switch (HomeAppLock.getMethod()) {
|
||||||
|
case PIN:
|
||||||
|
return R.string.home_lock_enter_pin;
|
||||||
|
case PATTERN:
|
||||||
|
return R.string.home_lock_enter_pattern;
|
||||||
|
case PASSWORD:
|
||||||
|
return R.string.home_lock_enter_password;
|
||||||
|
default:
|
||||||
|
return R.string.home_lock_enter_title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mMode == HomeAppLock.MODE_SET_PIN) {
|
||||||
|
return mStep == 0 ? R.string.home_lock_create_pin_subtitle
|
||||||
|
: R.string.home_lock_confirm_pin;
|
||||||
|
}
|
||||||
|
if (mMode == HomeAppLock.MODE_SET_PATTERN) {
|
||||||
|
return mStep == 0 ? R.string.home_lock_create_pattern_subtitle
|
||||||
|
: R.string.home_lock_confirm_pattern;
|
||||||
|
}
|
||||||
|
return mStep == 0 ? R.string.home_lock_create_password_subtitle
|
||||||
|
: R.string.home_lock_confirm_password;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isPinMode() {
|
||||||
|
return mMode == HomeAppLock.MODE_SET_PIN
|
||||||
|
|| (mMode == HomeAppLock.MODE_UNLOCK
|
||||||
|
&& HomeAppLock.getMethod() == HomeAppLock.Method.PIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isPatternMode() {
|
||||||
|
return mMode == HomeAppLock.MODE_SET_PATTERN
|
||||||
|
|| (mMode == HomeAppLock.MODE_UNLOCK
|
||||||
|
&& HomeAppLock.getMethod() == HomeAppLock.Method.PATTERN);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showError(final int messageRes) {
|
||||||
|
if (mError == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mError.setText(messageRes);
|
||||||
|
mError.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hideError() {
|
||||||
|
if (mError != null) {
|
||||||
|
mError.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showMismatch() {
|
||||||
|
new AlertDialog.Builder(this, R.style.Home_AlertDialog)
|
||||||
|
.setTitle(R.string.home_lock_mismatch_title)
|
||||||
|
.setMessage(R.string.home_lock_mismatch_message)
|
||||||
|
.setPositiveButton(android.R.string.ok, (dialog, which) -> resetSetup())
|
||||||
|
.setOnDismissListener(dialog -> resetSetup())
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetSetup() {
|
||||||
|
mStep = 0;
|
||||||
|
mPin = "";
|
||||||
|
mFirstSecret = "";
|
||||||
|
mFirstPattern.clear();
|
||||||
|
updatePinDots();
|
||||||
|
if (mPatternView != null) {
|
||||||
|
mPatternView.clear();
|
||||||
|
}
|
||||||
|
if (mPatternRecorded != null) {
|
||||||
|
mPatternRecorded.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
if (mPassword != null) {
|
||||||
|
mPassword.setText("");
|
||||||
|
}
|
||||||
|
refreshCopy();
|
||||||
|
hideError();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unlockAndFinish() {
|
||||||
|
HomeAppLock.markUnlocked();
|
||||||
|
HomeAppLock.markUnlockShowing(false);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,8 @@ package com.android.messaging.ui.home;
|
|||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
@@ -36,10 +38,14 @@ import com.android.messaging.ui.BugleActionBarActivity;
|
|||||||
import com.android.messaging.ui.ContactIconView;
|
import com.android.messaging.ui.ContactIconView;
|
||||||
import com.android.messaging.util.AvatarUriUtil;
|
import com.android.messaging.util.AvatarUriUtil;
|
||||||
import com.android.messaging.util.ContactRecipientEntryUtils;
|
import com.android.messaging.util.ContactRecipientEntryUtils;
|
||||||
|
import com.android.messaging.util.ContactUtil;
|
||||||
import com.android.messaging.util.ImeUtil;
|
import com.android.messaging.util.ImeUtil;
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetDialog;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
/** Home-style "new chat" screen: recipient field, group entry point and contact list. */
|
/** Home-style "new chat" screen: recipient field, group entry point and contact list. */
|
||||||
public class HomeComposeActivity extends BugleActionBarActivity
|
public class HomeComposeActivity extends BugleActionBarActivity
|
||||||
@@ -57,7 +63,11 @@ public class HomeComposeActivity extends BugleActionBarActivity
|
|||||||
private LinearLayout mChipContainer;
|
private LinearLayout mChipContainer;
|
||||||
private TextView mChipHint;
|
private TextView mChipHint;
|
||||||
private TextView mNextButton;
|
private TextView mNextButton;
|
||||||
|
private TextView mAccountLabel;
|
||||||
private boolean mGroupMode;
|
private boolean mGroupMode;
|
||||||
|
private ContactUtil.AccountFilter mAccountFilter;
|
||||||
|
private final ExecutorService mAccountExecutor = Executors.newSingleThreadExecutor();
|
||||||
|
private final Handler mMainHandler = new Handler(Looper.getMainLooper());
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(final Bundle savedInstanceState) {
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
@@ -125,6 +135,10 @@ public class HomeComposeActivity extends BugleActionBarActivity
|
|||||||
|
|
||||||
mCreateGroupButton = findViewById(R.id.home_compose_create_group);
|
mCreateGroupButton = findViewById(R.id.home_compose_create_group);
|
||||||
mCreateGroupButton.setOnClickListener(v -> enterGroupMode());
|
mCreateGroupButton.setOnClickListener(v -> enterGroupMode());
|
||||||
|
|
||||||
|
mAccountLabel = findViewById(R.id.home_compose_account_label);
|
||||||
|
final View accountChip = findViewById(R.id.home_compose_account_chip);
|
||||||
|
accountChip.setOnClickListener(v -> showAccountSheet());
|
||||||
}
|
}
|
||||||
|
|
||||||
private HomeContactsFragment getContactsFragment() {
|
private HomeContactsFragment getContactsFragment() {
|
||||||
@@ -132,6 +146,93 @@ public class HomeComposeActivity extends BugleActionBarActivity
|
|||||||
.findFragmentById(R.id.home_compose_fragment_host);
|
.findFragmentById(R.id.home_compose_fragment_host);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showAccountSheet() {
|
||||||
|
mAccountExecutor.execute(() -> {
|
||||||
|
final List<ContactUtil.ContactAccount> accounts =
|
||||||
|
ContactUtil.getContactAccounts(this);
|
||||||
|
mMainHandler.post(() -> presentAccountSheet(accounts));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void presentAccountSheet(final List<ContactUtil.ContactAccount> accounts) {
|
||||||
|
if (isFinishing() || isDestroyed()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final LayoutInflater inflater = LayoutInflater.from(this);
|
||||||
|
final LinearLayout list = (LinearLayout) inflater.inflate(
|
||||||
|
R.layout.home_compose_account_sheet, null, false);
|
||||||
|
final BottomSheetDialog[] dialog = new BottomSheetDialog[1];
|
||||||
|
|
||||||
|
int allCount = 0;
|
||||||
|
for (final ContactUtil.ContactAccount account : accounts) {
|
||||||
|
allCount += account.contactCount;
|
||||||
|
}
|
||||||
|
list.addView(createAccountRow(inflater, list,
|
||||||
|
getString(R.string.home_compose_all_contacts), null, allCount,
|
||||||
|
mAccountFilter == null, () -> {
|
||||||
|
applyAccountFilter(null, getString(R.string.home_compose_all_contacts));
|
||||||
|
if (dialog[0] != null) {
|
||||||
|
dialog[0].dismiss();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
for (final ContactUtil.ContactAccount account : accounts) {
|
||||||
|
final String title = account.deviceOnly
|
||||||
|
? getString(R.string.home_compose_device_contacts)
|
||||||
|
: (!TextUtils.isEmpty(account.title) ? account.title
|
||||||
|
: getString(R.string.home_compose_device_contacts));
|
||||||
|
final String subtitle = account.deviceOnly ? null : account.subtitle;
|
||||||
|
final boolean selected = mAccountFilter != null
|
||||||
|
&& account.toFilter().matches(mAccountFilter);
|
||||||
|
list.addView(createAccountRow(inflater, list, title, subtitle, account.contactCount,
|
||||||
|
selected, () -> {
|
||||||
|
applyAccountFilter(account.toFilter(), title);
|
||||||
|
if (dialog[0] != null) {
|
||||||
|
dialog[0].dismiss();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog[0] = HomeBottomSheet.builder(this)
|
||||||
|
.title(R.string.home_compose_contacts_source_title)
|
||||||
|
.custom(list)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private View createAccountRow(final LayoutInflater inflater, final LinearLayout parent,
|
||||||
|
final String title, final String subtitle, final int count, final boolean selected,
|
||||||
|
final Runnable onClick) {
|
||||||
|
final View row = inflater.inflate(R.layout.home_compose_account_row, parent, false);
|
||||||
|
((TextView) row.findViewById(R.id.home_compose_account_title)).setText(title);
|
||||||
|
final TextView subtitleView = row.findViewById(R.id.home_compose_account_subtitle);
|
||||||
|
if (TextUtils.isEmpty(subtitle)) {
|
||||||
|
subtitleView.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
subtitleView.setVisibility(View.VISIBLE);
|
||||||
|
subtitleView.setText(subtitle);
|
||||||
|
}
|
||||||
|
((TextView) row.findViewById(R.id.home_compose_account_count))
|
||||||
|
.setText(String.valueOf(count));
|
||||||
|
row.findViewById(R.id.home_compose_account_check)
|
||||||
|
.setVisibility(selected ? View.VISIBLE : View.GONE);
|
||||||
|
row.setOnClickListener(v -> onClick.run());
|
||||||
|
final LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||||
|
params.topMargin = parent.getChildCount() == 0
|
||||||
|
? 0 : getResources().getDimensionPixelSize(R.dimen.looply_space_sm);
|
||||||
|
row.setLayoutParams(params);
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyAccountFilter(final ContactUtil.AccountFilter filter, final String label) {
|
||||||
|
mAccountFilter = filter;
|
||||||
|
mAccountLabel.setText(label);
|
||||||
|
final HomeContactsFragment fragment = getContactsFragment();
|
||||||
|
if (fragment != null) {
|
||||||
|
fragment.setAccountFilter(filter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void enterGroupMode() {
|
private void enterGroupMode() {
|
||||||
mGroupMode = true;
|
mGroupMode = true;
|
||||||
ImeUtil.get().hideImeKeyboard(this, mRecipientField);
|
ImeUtil.get().hideImeKeyboard(this, mRecipientField);
|
||||||
@@ -226,6 +327,12 @@ public class HomeComposeActivity extends BugleActionBarActivity
|
|||||||
super.onBackPressed();
|
super.onBackPressed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
mAccountExecutor.shutdownNow();
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void finish() {
|
public void finish() {
|
||||||
if (mRecipientField != null && !TextUtils.isEmpty(mRecipientField.getText())) {
|
if (mRecipientField != null && !TextUtils.isEmpty(mRecipientField.getText())) {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import android.content.ContentUris;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Rect;
|
||||||
import android.graphics.drawable.ColorDrawable;
|
import android.graphics.drawable.ColorDrawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
@@ -44,6 +45,7 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.core.view.ViewCompat;
|
import androidx.core.view.ViewCompat;
|
||||||
|
import androidx.core.view.WindowInsetsAnimationCompat;
|
||||||
import androidx.core.view.WindowInsetsCompat;
|
import androidx.core.view.WindowInsetsCompat;
|
||||||
import androidx.recyclerview.widget.GridLayoutManager;
|
import androidx.recyclerview.widget.GridLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
@@ -77,6 +79,8 @@ public final class HomeComposeAttachPanel {
|
|||||||
void onTakePhoto();
|
void onTakePhoto();
|
||||||
void onRecordVideo();
|
void onRecordVideo();
|
||||||
void onOpenContact();
|
void onOpenContact();
|
||||||
|
void onShareLocation();
|
||||||
|
void onScheduleMessage();
|
||||||
void onRequestMediaPermission();
|
void onRequestMediaPermission();
|
||||||
void onComingSoon();
|
void onComingSoon();
|
||||||
void onDismiss();
|
void onDismiss();
|
||||||
@@ -247,6 +251,7 @@ public final class HomeComposeAttachPanel {
|
|||||||
float peekY;
|
float peekY;
|
||||||
int statusBarTop;
|
int statusBarTop;
|
||||||
int navigationBarBottom;
|
int navigationBarBottom;
|
||||||
|
int imeBottom;
|
||||||
final int dockBaseBottomPadding;
|
final int dockBaseBottomPadding;
|
||||||
final int originalStatusBarColor;
|
final int originalStatusBarColor;
|
||||||
boolean expanded;
|
boolean expanded;
|
||||||
@@ -328,6 +333,12 @@ public final class HomeComposeAttachPanel {
|
|||||||
if (send != null) {
|
if (send != null) {
|
||||||
send.setOnClickListener(v -> sendSelection());
|
send.setOnClickListener(v -> sendSelection());
|
||||||
}
|
}
|
||||||
|
overlay.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
|
||||||
|
if (closing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
applyImeLift(inferredImeHeight());
|
||||||
|
});
|
||||||
if (caption != null) {
|
if (caption != null) {
|
||||||
caption.setOnEditorActionListener((v, actionId, event) -> {
|
caption.setOnEditorActionListener((v, actionId, event) -> {
|
||||||
if (actionId == android.view.inputmethod.EditorInfo.IME_ACTION_SEND) {
|
if (actionId == android.view.inputmethod.EditorInfo.IME_ACTION_SEND) {
|
||||||
@@ -367,12 +378,21 @@ public final class HomeComposeAttachPanel {
|
|||||||
overlay.setLayoutParams(new FrameLayout.LayoutParams(
|
overlay.setLayoutParams(new FrameLayout.LayoutParams(
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(overlay, (v, insets) -> {
|
ViewCompat.setOnApplyWindowInsetsListener(overlay, (v, insets) -> {
|
||||||
statusBarTop = insets.getInsets(WindowInsetsCompat.Type.statusBars()).top;
|
applyWindowInsets(insets);
|
||||||
navigationBarBottom = insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom;
|
|
||||||
applyDockInsets();
|
|
||||||
applyExpandHeaderInsets(expanded);
|
|
||||||
return insets;
|
return insets;
|
||||||
});
|
});
|
||||||
|
ViewCompat.setWindowInsetsAnimationCallback(overlay,
|
||||||
|
new WindowInsetsAnimationCompat.Callback(
|
||||||
|
WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_CONTINUE_ON_SUBTREE) {
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public WindowInsetsCompat onProgress(
|
||||||
|
@NonNull final WindowInsetsCompat insets,
|
||||||
|
@NonNull final java.util.List<WindowInsetsAnimationCompat> running) {
|
||||||
|
applyWindowInsets(insets);
|
||||||
|
return insets;
|
||||||
|
}
|
||||||
|
});
|
||||||
ViewCompat.requestApplyInsets(overlay);
|
ViewCompat.requestApplyInsets(overlay);
|
||||||
bindHandleDrag(handleArea, this);
|
bindHandleDrag(handleArea, this);
|
||||||
bindHandleDrag(expandHeader, this);
|
bindHandleDrag(expandHeader, this);
|
||||||
@@ -568,9 +588,41 @@ public final class HomeComposeAttachPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void applyWindowInsets(final WindowInsetsCompat insets) {
|
||||||
|
statusBarTop = insets.getInsets(WindowInsetsCompat.Type.statusBars()).top;
|
||||||
|
navigationBarBottom = insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom;
|
||||||
|
applyImeLift(Math.max(
|
||||||
|
insets.getInsets(WindowInsetsCompat.Type.ime()).bottom,
|
||||||
|
inferredImeHeight()));
|
||||||
|
}
|
||||||
|
|
||||||
|
int inferredImeHeight() {
|
||||||
|
final View decor = activity.getWindow() == null
|
||||||
|
? null : activity.getWindow().getDecorView();
|
||||||
|
if (decor == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
final Rect visible = new Rect();
|
||||||
|
decor.getWindowVisibleDisplayFrame(visible);
|
||||||
|
final int covered = decor.getHeight() - visible.bottom;
|
||||||
|
return covered > 80 ? covered : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void applyImeLift(final int ime) {
|
||||||
|
if (imeBottom == ime) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
imeBottom = ime;
|
||||||
|
applyBottomMargin(sheet, imeBottom);
|
||||||
|
applyBottomMargin(dock, imeBottom);
|
||||||
|
applyDockInsets();
|
||||||
|
applyExpandHeaderInsets(expanded);
|
||||||
|
}
|
||||||
|
|
||||||
void applyDockInsets() {
|
void applyDockInsets() {
|
||||||
if (dock != null) {
|
if (dock != null) {
|
||||||
final int bottom = dockBaseBottomPadding + navigationBarBottom;
|
final int bottom = dockBaseBottomPadding
|
||||||
|
+ (imeBottom > 0 ? 0 : navigationBarBottom);
|
||||||
if (dock.getPaddingBottom() != bottom) {
|
if (dock.getPaddingBottom() != bottom) {
|
||||||
dock.setPaddingRelative(
|
dock.setPaddingRelative(
|
||||||
dock.getPaddingStart(),
|
dock.getPaddingStart(),
|
||||||
@@ -590,6 +642,18 @@ public final class HomeComposeAttachPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void applyBottomMargin(final View view, final int bottom) {
|
||||||
|
if (view == null || !(view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final ViewGroup.MarginLayoutParams lp =
|
||||||
|
(ViewGroup.MarginLayoutParams) view.getLayoutParams();
|
||||||
|
if (lp.bottomMargin != bottom) {
|
||||||
|
lp.bottomMargin = bottom;
|
||||||
|
view.setLayoutParams(lp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void applyGridDockPadding() {
|
void applyGridDockPadding() {
|
||||||
if (grid == null) {
|
if (grid == null) {
|
||||||
return;
|
return;
|
||||||
@@ -982,6 +1046,12 @@ public final class HomeComposeAttachPanel {
|
|||||||
} else if (index == TAB_CONTACT) {
|
} else if (index == TAB_CONTACT) {
|
||||||
host.onOpenContact();
|
host.onOpenContact();
|
||||||
dismissAnimated.run();
|
dismissAnimated.run();
|
||||||
|
} else if (index == TAB_LOCATION) {
|
||||||
|
host.onShareLocation();
|
||||||
|
dismissAnimated.run();
|
||||||
|
} else if (index == TAB_SCHEDULE) {
|
||||||
|
host.onScheduleMessage();
|
||||||
|
dismissAnimated.run();
|
||||||
} else if (index != TAB_GALLERY) {
|
} else if (index != TAB_GALLERY) {
|
||||||
host.onComingSoon();
|
host.onComingSoon();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,24 +6,74 @@ import android.view.LayoutInflater;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.CursorAdapter;
|
import android.widget.CursorAdapter;
|
||||||
|
import android.widget.SectionIndexer;
|
||||||
|
|
||||||
import com.android.messaging.R;
|
import com.android.messaging.R;
|
||||||
import com.android.messaging.ui.contact.ContactListItemView;
|
import com.android.messaging.ui.contact.ContactListItemView;
|
||||||
|
import com.android.messaging.ui.contact.ContactSectionIndexer;
|
||||||
import com.android.messaging.util.Assert;
|
import com.android.messaging.util.Assert;
|
||||||
|
|
||||||
public class HomeContactListAdapter extends CursorAdapter {
|
public class HomeContactListAdapter extends CursorAdapter implements SectionIndexer {
|
||||||
private final ContactListItemView.HostInterface mHost;
|
private final ContactListItemView.HostInterface mHost;
|
||||||
|
private ContactSectionIndexer mSectionIndexer;
|
||||||
|
private boolean mNeedAlphabetHeader = true;
|
||||||
|
|
||||||
public HomeContactListAdapter(final Context context, final Cursor cursor,
|
public HomeContactListAdapter(final Context context, final Cursor cursor,
|
||||||
final ContactListItemView.HostInterface host) {
|
final ContactListItemView.HostInterface host) {
|
||||||
super(context, cursor, 0);
|
super(context, cursor, 0);
|
||||||
mHost = host;
|
mHost = host;
|
||||||
|
mSectionIndexer = new ContactSectionIndexer(cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNeedAlphabetHeader(final boolean needAlphabetHeader) {
|
||||||
|
if (mNeedAlphabetHeader == needAlphabetHeader) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mNeedAlphabetHeader = needAlphabetHeader;
|
||||||
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void bindView(final View view, final Context context, final Cursor cursor) {
|
public void bindView(final View view, final Context context, final Cursor cursor) {
|
||||||
Assert.isTrue(view instanceof ContactListItemView);
|
Assert.isTrue(view instanceof ContactListItemView);
|
||||||
((ContactListItemView) view).bind(cursor, mHost, false, null);
|
String alphabetHeader = null;
|
||||||
|
if (mNeedAlphabetHeader && mSectionIndexer.getSections().length > 0) {
|
||||||
|
final int position = cursor.getPosition();
|
||||||
|
final int section = mSectionIndexer.getSectionForPosition(position);
|
||||||
|
if (section >= 0 && mSectionIndexer.getPositionForSection(section) == position) {
|
||||||
|
alphabetHeader = (String) mSectionIndexer.getSections()[section];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final ContactListItemView itemView = (ContactListItemView) view;
|
||||||
|
itemView.bind(cursor, mHost, mNeedAlphabetHeader, alphabetHeader);
|
||||||
|
itemView.setSectionBox(sectionRole(cursor.getPosition()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private int sectionRole(final int position) {
|
||||||
|
if (!mNeedAlphabetHeader || mSectionIndexer.getSections().length == 0) {
|
||||||
|
return ContactListItemView.SECTION_SINGLE;
|
||||||
|
}
|
||||||
|
final int section = mSectionIndexer.getSectionForPosition(position);
|
||||||
|
if (section < 0) {
|
||||||
|
return ContactListItemView.SECTION_SINGLE;
|
||||||
|
}
|
||||||
|
final int first = mSectionIndexer.getPositionForSection(section);
|
||||||
|
final int last;
|
||||||
|
if (section + 1 < mSectionIndexer.getSections().length) {
|
||||||
|
last = mSectionIndexer.getPositionForSection(section + 1) - 1;
|
||||||
|
} else {
|
||||||
|
last = getCount() - 1;
|
||||||
|
}
|
||||||
|
if (position == first && position == last) {
|
||||||
|
return ContactListItemView.SECTION_SINGLE;
|
||||||
|
}
|
||||||
|
if (position == first) {
|
||||||
|
return ContactListItemView.SECTION_FIRST;
|
||||||
|
}
|
||||||
|
if (position == last) {
|
||||||
|
return ContactListItemView.SECTION_LAST;
|
||||||
|
}
|
||||||
|
return ContactListItemView.SECTION_MIDDLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -31,4 +81,25 @@ public class HomeContactListAdapter extends CursorAdapter {
|
|||||||
return LayoutInflater.from(context).inflate(R.layout.home_contacts_list_item, parent,
|
return LayoutInflater.from(context).inflate(R.layout.home_contacts_list_item, parent,
|
||||||
false);
|
false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cursor swapCursor(final Cursor newCursor) {
|
||||||
|
mSectionIndexer = new ContactSectionIndexer(newCursor);
|
||||||
|
return super.swapCursor(newCursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object[] getSections() {
|
||||||
|
return mSectionIndexer.getSections();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPositionForSection(final int sectionIndex) {
|
||||||
|
return mSectionIndexer.getPositionForSection(sectionIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSectionForPosition(final int position) {
|
||||||
|
return mSectionIndexer.getSectionForPosition(position);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,12 +114,22 @@ public class HomeContactsFragment extends Fragment implements ContactPickerDataL
|
|||||||
/** Narrows the list as the host screen's recipient field is typed into. */
|
/** Narrows the list as the host screen's recipient field is typed into. */
|
||||||
public void setFilterText(final String filterText) {
|
public void setFilterText(final String filterText) {
|
||||||
mFilterText = filterText;
|
mFilterText = filterText;
|
||||||
|
if (mAdapter != null) {
|
||||||
|
mAdapter.setNeedAlphabetHeader(TextUtils.isEmpty(filterText));
|
||||||
|
}
|
||||||
if (mBinding.isBound()) {
|
if (mBinding.isBound()) {
|
||||||
mBinding.getData().setSearchText(mBinding, filterText);
|
mBinding.getData().setSearchText(mBinding, filterText);
|
||||||
}
|
}
|
||||||
updateResultViews();
|
updateResultViews();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Limits the list to one contacts account, or every account when {@code filter} is null. */
|
||||||
|
public void setAccountFilter(final ContactUtil.AccountFilter filter) {
|
||||||
|
if (mBinding.isBound()) {
|
||||||
|
mBinding.getData().setAccountFilter(mBinding, filter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts a conversation with the raw text currently typed, if it can be messaged.
|
* Starts a conversation with the raw text currently typed, if it can be messaged.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.android.messaging.ui.home;
|
package com.android.messaging.ui.home;
|
||||||
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -43,14 +42,20 @@ public final class HomeInputFocus {
|
|||||||
if (isInside(box, ev)) {
|
if (isInside(box, ev)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
root.setFocusable(true);
|
final View sink = focusSink(root);
|
||||||
root.setFocusableInTouchMode(true);
|
sink.setFocusable(true);
|
||||||
|
sink.setFocusableInTouchMode(true);
|
||||||
|
sink.requestFocus();
|
||||||
focused.clearFocus();
|
focused.clearFocus();
|
||||||
root.requestFocus();
|
|
||||||
ImeUtil.get().hideImeKeyboard(focused.getContext(), focused);
|
ImeUtil.get().hideImeKeyboard(focused.getContext(), focused);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static View focusSink(@NonNull final View root) {
|
||||||
|
final View content = root.findViewById(android.R.id.content);
|
||||||
|
return content != null ? content : root;
|
||||||
|
}
|
||||||
|
|
||||||
private static View findBox(final View focused) {
|
private static View findBox(final View focused) {
|
||||||
ViewParent parent = focused.getParent();
|
ViewParent parent = focused.getParent();
|
||||||
while (parent instanceof ViewGroup) {
|
while (parent instanceof ViewGroup) {
|
||||||
@@ -64,11 +69,13 @@ public final class HomeInputFocus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isInside(final View view, final MotionEvent ev) {
|
private static boolean isInside(final View view, final MotionEvent ev) {
|
||||||
final Rect rect = new Rect();
|
final int[] loc = new int[2];
|
||||||
if (!view.getGlobalVisibleRect(rect)) {
|
view.getLocationOnScreen(loc);
|
||||||
return false;
|
final int x = Math.round(ev.getRawX());
|
||||||
}
|
final int y = Math.round(ev.getRawY());
|
||||||
return rect.contains(Math.round(ev.getRawX()), Math.round(ev.getRawY()));
|
return x >= loc[0] && y >= loc[1]
|
||||||
|
&& x < loc[0] + view.getWidth()
|
||||||
|
&& y < loc[1] + view.getHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class Installed extends WindowCallbackWrapper {
|
private static final class Installed extends WindowCallbackWrapper {
|
||||||
|
|||||||
@@ -0,0 +1,278 @@
|
|||||||
|
package com.android.messaging.ui.home;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.location.Address;
|
||||||
|
import android.location.Geocoder;
|
||||||
|
import android.location.Location;
|
||||||
|
import android.location.LocationManager;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.webkit.JavascriptInterface;
|
||||||
|
import android.webkit.WebSettings;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
import android.webkit.WebViewClient;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.android.messaging.R;
|
||||||
|
import com.android.messaging.datamodel.MediaScratchFileProvider;
|
||||||
|
import com.android.messaging.ui.BugleActionBarActivity;
|
||||||
|
import com.android.messaging.ui.UIIntents;
|
||||||
|
import com.android.messaging.util.LogUtil;
|
||||||
|
import com.android.messaging.util.OsUtil;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
/** Full-screen map picker that returns a maps URL and optional preview image. */
|
||||||
|
public class HomeLocationActivity extends BugleActionBarActivity {
|
||||||
|
private static final double DEFAULT_LAT = 37.5665;
|
||||||
|
private static final double DEFAULT_LNG = 126.9780;
|
||||||
|
private static final int STATIC_MAP_TIMEOUT_MS = 8000;
|
||||||
|
|
||||||
|
private WebView mMap;
|
||||||
|
private TextView mAddressView;
|
||||||
|
private ImageButton mSendButton;
|
||||||
|
private final Handler mMain = new Handler(Looper.getMainLooper());
|
||||||
|
private final ExecutorService mIo = Executors.newSingleThreadExecutor();
|
||||||
|
private double mLat = DEFAULT_LAT;
|
||||||
|
private double mLng = DEFAULT_LNG;
|
||||||
|
private String mAddress = "";
|
||||||
|
private boolean mMapReady;
|
||||||
|
private boolean mSending;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.home_location_activity);
|
||||||
|
if (getSupportActionBar() != null) {
|
||||||
|
getSupportActionBar().hide();
|
||||||
|
}
|
||||||
|
HomeSubHeader.bind(this, R.string.home_location_title, v -> finish());
|
||||||
|
|
||||||
|
mAddressView = findViewById(R.id.home_location_address);
|
||||||
|
mSendButton = findViewById(R.id.home_location_send);
|
||||||
|
mMap = findViewById(R.id.home_location_map);
|
||||||
|
mSendButton.setOnClickListener(v -> sendCurrentLocation());
|
||||||
|
findViewById(R.id.home_location_my_location).setOnClickListener(
|
||||||
|
v -> moveToMyLocation(true));
|
||||||
|
setupMap();
|
||||||
|
moveToMyLocation(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint({"SetJavaScriptEnabled", "AddJavascriptInterface"})
|
||||||
|
private void setupMap() {
|
||||||
|
final WebSettings settings = mMap.getSettings();
|
||||||
|
settings.setJavaScriptEnabled(true);
|
||||||
|
settings.setDomStorageEnabled(true);
|
||||||
|
settings.setAllowFileAccess(true);
|
||||||
|
mMap.addJavascriptInterface(new MapBridge(), "LocationBridge");
|
||||||
|
mMap.setWebViewClient(new WebViewClient() {
|
||||||
|
@Override
|
||||||
|
public void onPageFinished(final WebView view, final String url) {
|
||||||
|
mMapReady = true;
|
||||||
|
applyMapCenter(mLat, mLng);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mMap.loadUrl("file:///android_asset/home_location_map.html");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void moveToMyLocation(final boolean userRequested) {
|
||||||
|
if (!OsUtil.hasLocationPermission()) {
|
||||||
|
if (userRequested) {
|
||||||
|
HomeToast.show(this, R.string.home_location_permission_denied);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final LocationManager manager = getSystemService(LocationManager.class);
|
||||||
|
if (manager == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Location best = null;
|
||||||
|
try {
|
||||||
|
best = manager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
|
||||||
|
if (best == null) {
|
||||||
|
best = manager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
|
||||||
|
}
|
||||||
|
if (best == null) {
|
||||||
|
best = manager.getLastKnownLocation(LocationManager.PASSIVE_PROVIDER);
|
||||||
|
}
|
||||||
|
} catch (final SecurityException exception) {
|
||||||
|
LogUtil.w(LogUtil.BUGLE_TAG, "Location lookup failed", exception);
|
||||||
|
}
|
||||||
|
if (best != null) {
|
||||||
|
mLat = best.getLatitude();
|
||||||
|
mLng = best.getLongitude();
|
||||||
|
applyMapCenter(mLat, mLng);
|
||||||
|
reverseGeocode(mLat, mLng);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (userRequested) {
|
||||||
|
HomeToast.show(this, R.string.home_location_unavailable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyMapCenter(final double lat, final double lng) {
|
||||||
|
if (!mMapReady) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mMap.evaluateJavascript("setCenter(" + lat + "," + lng + ")", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Runnable mPendingGeocode;
|
||||||
|
|
||||||
|
private void reverseGeocode(final double lat, final double lng) {
|
||||||
|
if (mPendingGeocode != null) {
|
||||||
|
mMain.removeCallbacks(mPendingGeocode);
|
||||||
|
}
|
||||||
|
mPendingGeocode = () -> reverseGeocodeNow(lat, lng);
|
||||||
|
mMain.postDelayed(mPendingGeocode, 350);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reverseGeocodeNow(final double lat, final double lng) {
|
||||||
|
mAddressView.setText(R.string.home_location_finding);
|
||||||
|
mIo.execute(() -> {
|
||||||
|
String address = formatLatLng(lat, lng);
|
||||||
|
try {
|
||||||
|
if (Geocoder.isPresent()) {
|
||||||
|
final List<Address> results = new Geocoder(this, Locale.getDefault())
|
||||||
|
.getFromLocation(lat, lng, 1);
|
||||||
|
if (results != null && !results.isEmpty()) {
|
||||||
|
final String line = results.get(0).getAddressLine(0);
|
||||||
|
if (!TextUtils.isEmpty(line)) {
|
||||||
|
address = line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (final Exception exception) {
|
||||||
|
LogUtil.w(LogUtil.BUGLE_TAG, "Geocoder failed", exception);
|
||||||
|
}
|
||||||
|
final String resolved = address;
|
||||||
|
mMain.post(() -> {
|
||||||
|
mAddress = resolved;
|
||||||
|
mAddressView.setText(resolved);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendCurrentLocation() {
|
||||||
|
if (mSending) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mSending = true;
|
||||||
|
mSendButton.setEnabled(false);
|
||||||
|
final double lat = mLat;
|
||||||
|
final double lng = mLng;
|
||||||
|
final String address = TextUtils.isEmpty(mAddress) ? formatLatLng(lat, lng) : mAddress;
|
||||||
|
final String mapsUrl = "https://maps.google.com/maps?q=" + lat + "," + lng;
|
||||||
|
final String text = address + "\n" + mapsUrl;
|
||||||
|
mIo.execute(() -> {
|
||||||
|
final Uri image = downloadStaticMap(lat, lng);
|
||||||
|
mMain.post(() -> {
|
||||||
|
final Intent data = new Intent();
|
||||||
|
data.putExtra(UIIntents.UI_INTENT_EXTRA_LOCATION_TEXT, text);
|
||||||
|
if (image != null) {
|
||||||
|
data.putExtra(UIIntents.UI_INTENT_EXTRA_LOCATION_IMAGE_URI, image);
|
||||||
|
}
|
||||||
|
setResult(RESULT_OK, data);
|
||||||
|
finish();
|
||||||
|
overridePendingTransition(R.anim.home_activity_hold, R.anim.home_activity_close);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Uri downloadStaticMap(final double lat, final double lng) {
|
||||||
|
HttpURLConnection connection = null;
|
||||||
|
try {
|
||||||
|
final String url = "https://staticmap.openstreetmap.de/staticmap.php?center="
|
||||||
|
+ lat + "," + lng + "&zoom=16&size=640x360&markers=" + lat + "," + lng
|
||||||
|
+ ",red-pushpin";
|
||||||
|
connection = (HttpURLConnection) new URL(url).openConnection();
|
||||||
|
connection.setConnectTimeout(STATIC_MAP_TIMEOUT_MS);
|
||||||
|
connection.setReadTimeout(STATIC_MAP_TIMEOUT_MS);
|
||||||
|
connection.connect();
|
||||||
|
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final String contentType = connection.getContentType();
|
||||||
|
if (contentType == null || !contentType.contains("image")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final Uri uri = MediaScratchFileProvider.buildMediaScratchSpaceUri("jpg");
|
||||||
|
final File file = MediaScratchFileProvider.getFileFromUri(uri);
|
||||||
|
if (file == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try (InputStream in = connection.getInputStream();
|
||||||
|
FileOutputStream out = new FileOutputStream(file)) {
|
||||||
|
final byte[] buffer = new byte[8192];
|
||||||
|
int read;
|
||||||
|
while ((read = in.read(buffer)) != -1) {
|
||||||
|
out.write(buffer, 0, read);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return uri;
|
||||||
|
} catch (final Exception exception) {
|
||||||
|
LogUtil.w(LogUtil.BUGLE_TAG, "Static map download failed", exception);
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
if (connection != null) {
|
||||||
|
connection.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String formatLatLng(final double lat, final double lng) {
|
||||||
|
return String.format(Locale.US, "%.5f, %.5f", lat, lng);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
if (mMap != null) {
|
||||||
|
mMap.onResume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
if (mMap != null) {
|
||||||
|
mMap.onPause();
|
||||||
|
}
|
||||||
|
super.onPause();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
if (mMap != null) {
|
||||||
|
mMap.destroy();
|
||||||
|
}
|
||||||
|
mIo.shutdownNow();
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class MapBridge {
|
||||||
|
@JavascriptInterface
|
||||||
|
public void onMapMoved(final double lat, final double lng) {
|
||||||
|
mMain.post(() -> {
|
||||||
|
mLat = lat;
|
||||||
|
mLng = lng;
|
||||||
|
reverseGeocode(lat, lng);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.android.messaging.ui.home;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
/** Overlay that draws but never takes touches. */
|
||||||
|
public class HomePassThroughView extends View {
|
||||||
|
public HomePassThroughView(final Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HomePassThroughView(final Context context, final AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HomePassThroughView(final Context context, final AttributeSet attrs,
|
||||||
|
final int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean dispatchTouchEvent(final MotionEvent event) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
package com.android.messaging.ui.home;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Path;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.android.messaging.R;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/** 3x3 pattern lock grid with mid-cell bridging (File Plus behavior). */
|
||||||
|
public class HomePatternLockView extends View {
|
||||||
|
|
||||||
|
public interface Listener {
|
||||||
|
void onPatternChanged(@NonNull List<Integer> cells);
|
||||||
|
|
||||||
|
void onPatternComplete(@NonNull List<Integer> cells);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Paint mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
private final Paint mDragPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
private final Paint mHaloPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
private final Paint mDotPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
private final Paint mInnerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
private final Path mPath = new Path();
|
||||||
|
private final float[] mCentersX = new float[9];
|
||||||
|
private final float[] mCentersY = new float[9];
|
||||||
|
private final List<Integer> mCells = new ArrayList<>();
|
||||||
|
|
||||||
|
private Listener mListener;
|
||||||
|
private boolean mDragging;
|
||||||
|
private float mDragX;
|
||||||
|
private float mDragY;
|
||||||
|
private float mHitRadius;
|
||||||
|
private float mCoreRadius;
|
||||||
|
private float mSelectedRadius;
|
||||||
|
private float mInnerRadius;
|
||||||
|
private float mHaloGap;
|
||||||
|
private int mAccent;
|
||||||
|
private int mIdle;
|
||||||
|
|
||||||
|
public HomePatternLockView(final Context context) {
|
||||||
|
super(context);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public HomePatternLockView(final Context context, @Nullable final AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public HomePatternLockView(final Context context, @Nullable final AttributeSet attrs,
|
||||||
|
final int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
mAccent = getContext().getColor(R.color.home_accent);
|
||||||
|
mIdle = getContext().getColor(R.color.home_divider);
|
||||||
|
mLinePaint.setStyle(Paint.Style.STROKE);
|
||||||
|
mLinePaint.setStrokeCap(Paint.Cap.ROUND);
|
||||||
|
mLinePaint.setStrokeJoin(Paint.Join.ROUND);
|
||||||
|
mLinePaint.setColor(mAccent);
|
||||||
|
mLinePaint.setStrokeWidth(dp(4));
|
||||||
|
mDragPaint.set(mLinePaint);
|
||||||
|
mDragPaint.setAlpha(217);
|
||||||
|
mHaloPaint.setStyle(Paint.Style.STROKE);
|
||||||
|
mHaloPaint.setStrokeWidth(dp(3));
|
||||||
|
mHaloPaint.setColor(mAccent);
|
||||||
|
mDotPaint.setStyle(Paint.Style.FILL);
|
||||||
|
mInnerPaint.setStyle(Paint.Style.FILL);
|
||||||
|
mInnerPaint.setColor(0xFFFFFFFF);
|
||||||
|
mHitRadius = dp(32);
|
||||||
|
mCoreRadius = dp(6);
|
||||||
|
mSelectedRadius = dp(10);
|
||||||
|
mInnerRadius = dp(3.5f);
|
||||||
|
mHaloGap = dp(5);
|
||||||
|
setClickable(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setListener(@Nullable final Listener listener) {
|
||||||
|
mListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
mCells.clear();
|
||||||
|
mDragging = false;
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public List<Integer> getCells() {
|
||||||
|
return new ArrayList<>(mCells);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
|
||||||
|
final int width = MeasureSpec.getSize(widthMeasureSpec);
|
||||||
|
final int height = MeasureSpec.getSize(heightMeasureSpec);
|
||||||
|
final int size;
|
||||||
|
if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.UNSPECIFIED) {
|
||||||
|
size = width;
|
||||||
|
} else {
|
||||||
|
size = Math.min(width, height);
|
||||||
|
}
|
||||||
|
setMeasuredDimension(size, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSizeChanged(final int w, final int h, final int oldw, final int oldh) {
|
||||||
|
super.onSizeChanged(w, h, oldw, oldh);
|
||||||
|
final float spacingX = w / 4f;
|
||||||
|
final float spacingY = h / 4f;
|
||||||
|
final float cell = Math.min(spacingX, spacingY);
|
||||||
|
mHitRadius = cell * 0.42f;
|
||||||
|
mCoreRadius = cell * 0.12f;
|
||||||
|
mSelectedRadius = cell * 0.18f;
|
||||||
|
mInnerRadius = cell * 0.07f;
|
||||||
|
mHaloGap = cell * 0.08f;
|
||||||
|
mLinePaint.setStrokeWidth(Math.max(dp(4), cell * 0.08f));
|
||||||
|
mDragPaint.setStrokeWidth(mLinePaint.getStrokeWidth());
|
||||||
|
for (int i = 0; i < 9; i++) {
|
||||||
|
mCentersX[i] = spacingX * ((i % 3) + 1);
|
||||||
|
mCentersY[i] = spacingY * ((i / 3) + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDraw(@NonNull final Canvas canvas) {
|
||||||
|
super.onDraw(canvas);
|
||||||
|
if (mCells.size() >= 2) {
|
||||||
|
mPath.reset();
|
||||||
|
mPath.moveTo(mCentersX[mCells.get(0)], mCentersY[mCells.get(0)]);
|
||||||
|
for (int i = 1; i < mCells.size(); i++) {
|
||||||
|
final int cell = mCells.get(i);
|
||||||
|
mPath.lineTo(mCentersX[cell], mCentersY[cell]);
|
||||||
|
}
|
||||||
|
canvas.drawPath(mPath, mLinePaint);
|
||||||
|
}
|
||||||
|
if (mDragging && !mCells.isEmpty()) {
|
||||||
|
final int last = mCells.get(mCells.size() - 1);
|
||||||
|
canvas.drawLine(mCentersX[last], mCentersY[last], mDragX, mDragY, mDragPaint);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 9; i++) {
|
||||||
|
final boolean selected = mCells.contains(i);
|
||||||
|
final float core = selected ? mSelectedRadius : mCoreRadius;
|
||||||
|
mHaloPaint.setAlpha(selected ? 97 : 51);
|
||||||
|
canvas.drawCircle(mCentersX[i], mCentersY[i], core + mHaloGap, mHaloPaint);
|
||||||
|
mDotPaint.setColor(selected ? mAccent : mIdle);
|
||||||
|
canvas.drawCircle(mCentersX[i], mCentersY[i], core, mDotPaint);
|
||||||
|
if (selected) {
|
||||||
|
canvas.drawCircle(mCentersX[i], mCentersY[i], mInnerRadius, mInnerPaint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onTouchEvent(final MotionEvent event) {
|
||||||
|
switch (event.getActionMasked()) {
|
||||||
|
case MotionEvent.ACTION_DOWN:
|
||||||
|
mDragging = true;
|
||||||
|
mDragX = event.getX();
|
||||||
|
mDragY = event.getY();
|
||||||
|
mCells.clear();
|
||||||
|
addCellAt(mDragX, mDragY);
|
||||||
|
invalidate();
|
||||||
|
return true;
|
||||||
|
case MotionEvent.ACTION_MOVE:
|
||||||
|
mDragX = event.getX();
|
||||||
|
mDragY = event.getY();
|
||||||
|
addCellAt(mDragX, mDragY);
|
||||||
|
invalidate();
|
||||||
|
return true;
|
||||||
|
case MotionEvent.ACTION_UP:
|
||||||
|
case MotionEvent.ACTION_CANCEL:
|
||||||
|
mDragging = false;
|
||||||
|
invalidate();
|
||||||
|
if (mListener != null && !mCells.isEmpty()) {
|
||||||
|
mListener.onPatternComplete(getCells());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return super.onTouchEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addCellAt(final float x, final float y) {
|
||||||
|
final Integer hit = cellAt(x, y);
|
||||||
|
if (hit == null || mCells.contains(hit)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final List<Integer> next = HomeAppLock.appendPatternCells(mCells, hit);
|
||||||
|
mCells.clear();
|
||||||
|
mCells.addAll(next);
|
||||||
|
if (mListener != null) {
|
||||||
|
mListener.onPatternChanged(getCells());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Integer cellAt(final float x, final float y) {
|
||||||
|
for (int i = 0; i < 9; i++) {
|
||||||
|
final float dx = x - mCentersX[i];
|
||||||
|
final float dy = y - mCentersY[i];
|
||||||
|
if (dx * dx + dy * dy <= mHitRadius * mHitRadius) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private float dp(final float value) {
|
||||||
|
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value,
|
||||||
|
getResources().getDisplayMetrics());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
package com.android.messaging.ui.home;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.text.format.DateFormat;
|
||||||
|
import android.text.format.DateUtils;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.DatePicker;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.TimePicker;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.android.messaging.R;
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetDialog;
|
||||||
|
|
||||||
|
import java.util.Calendar;
|
||||||
|
|
||||||
|
/** Home date then time sheets used by Schedule in the compose attach panel. */
|
||||||
|
public final class HomeSchedulePicker {
|
||||||
|
|
||||||
|
public interface Callback {
|
||||||
|
void onScheduled(long sendAtMillis);
|
||||||
|
}
|
||||||
|
|
||||||
|
private HomeSchedulePicker() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void show(@NonNull final Context context, final long existingSendAt,
|
||||||
|
@NonNull final Callback callback) {
|
||||||
|
final Calendar calendar = Calendar.getInstance();
|
||||||
|
if (existingSendAt > System.currentTimeMillis()) {
|
||||||
|
calendar.setTimeInMillis(existingSendAt);
|
||||||
|
} else {
|
||||||
|
calendar.add(Calendar.HOUR_OF_DAY, 1);
|
||||||
|
calendar.set(Calendar.MINUTE, 0);
|
||||||
|
calendar.set(Calendar.SECOND, 0);
|
||||||
|
calendar.set(Calendar.MILLISECOND, 0);
|
||||||
|
}
|
||||||
|
showDate(context, calendar, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatChip(@NonNull final Context context, final long sendAtMillis) {
|
||||||
|
final String time = DateFormat.getTimeFormat(context).format(sendAtMillis);
|
||||||
|
if (DateUtils.isToday(sendAtMillis)) {
|
||||||
|
return context.getString(R.string.home_schedule_today, time);
|
||||||
|
}
|
||||||
|
final Calendar tomorrow = Calendar.getInstance();
|
||||||
|
tomorrow.add(Calendar.DAY_OF_YEAR, 1);
|
||||||
|
final Calendar target = Calendar.getInstance();
|
||||||
|
target.setTimeInMillis(sendAtMillis);
|
||||||
|
if (tomorrow.get(Calendar.YEAR) == target.get(Calendar.YEAR)
|
||||||
|
&& tomorrow.get(Calendar.DAY_OF_YEAR) == target.get(Calendar.DAY_OF_YEAR)) {
|
||||||
|
return context.getString(R.string.home_schedule_tomorrow, time);
|
||||||
|
}
|
||||||
|
final String date = DateUtils.formatDateTime(context, sendAtMillis,
|
||||||
|
DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_MONTH
|
||||||
|
| DateUtils.FORMAT_NO_YEAR);
|
||||||
|
return date + " " + time;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void showDate(@NonNull final Context context, @NonNull final Calendar calendar,
|
||||||
|
@NonNull final Callback callback) {
|
||||||
|
final View body = LayoutInflater.from(context).inflate(
|
||||||
|
R.layout.home_schedule_date_sheet, null, false);
|
||||||
|
final DatePicker picker = body.findViewById(R.id.home_schedule_date);
|
||||||
|
picker.setMinDate(System.currentTimeMillis() - 1000L);
|
||||||
|
picker.updateDate(
|
||||||
|
calendar.get(Calendar.YEAR),
|
||||||
|
calendar.get(Calendar.MONTH),
|
||||||
|
calendar.get(Calendar.DAY_OF_MONTH));
|
||||||
|
final BottomSheetDialog[] dialogHolder = new BottomSheetDialog[1];
|
||||||
|
dialogHolder[0] = HomeBottomSheet.builder(context)
|
||||||
|
.title(R.string.home_schedule_select_date)
|
||||||
|
.custom(body)
|
||||||
|
.show();
|
||||||
|
bindActions(body,
|
||||||
|
v -> dialogHolder[0].dismiss(),
|
||||||
|
v -> {
|
||||||
|
calendar.set(Calendar.YEAR, picker.getYear());
|
||||||
|
calendar.set(Calendar.MONTH, picker.getMonth());
|
||||||
|
calendar.set(Calendar.DAY_OF_MONTH, picker.getDayOfMonth());
|
||||||
|
dialogHolder[0].dismiss();
|
||||||
|
showTime(context, calendar, callback);
|
||||||
|
},
|
||||||
|
R.string.home_schedule_next);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void showTime(@NonNull final Context context, @NonNull final Calendar calendar,
|
||||||
|
@NonNull final Callback callback) {
|
||||||
|
final View body = LayoutInflater.from(context).inflate(
|
||||||
|
R.layout.home_schedule_time_sheet, null, false);
|
||||||
|
final TimePicker picker = body.findViewById(R.id.home_schedule_time);
|
||||||
|
picker.setIs24HourView(DateFormat.is24HourFormat(context));
|
||||||
|
picker.setHour(calendar.get(Calendar.HOUR_OF_DAY));
|
||||||
|
picker.setMinute(calendar.get(Calendar.MINUTE));
|
||||||
|
final BottomSheetDialog[] dialogHolder = new BottomSheetDialog[1];
|
||||||
|
dialogHolder[0] = HomeBottomSheet.builder(context)
|
||||||
|
.title(R.string.home_schedule_select_time)
|
||||||
|
.custom(body)
|
||||||
|
.show();
|
||||||
|
bindActions(body,
|
||||||
|
v -> {
|
||||||
|
dialogHolder[0].dismiss();
|
||||||
|
showDate(context, calendar, callback);
|
||||||
|
},
|
||||||
|
v -> {
|
||||||
|
calendar.set(Calendar.HOUR_OF_DAY, picker.getHour());
|
||||||
|
calendar.set(Calendar.MINUTE, picker.getMinute());
|
||||||
|
calendar.set(Calendar.SECOND, 0);
|
||||||
|
calendar.set(Calendar.MILLISECOND, 0);
|
||||||
|
final long sendAt = calendar.getTimeInMillis();
|
||||||
|
if (sendAt <= System.currentTimeMillis()) {
|
||||||
|
HomeToast.show(context, R.string.home_schedule_past);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dialogHolder[0].dismiss();
|
||||||
|
callback.onScheduled(sendAt);
|
||||||
|
},
|
||||||
|
R.string.home_schedule_done);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void bindActions(@NonNull final View body,
|
||||||
|
@NonNull final View.OnClickListener onCancel,
|
||||||
|
@NonNull final View.OnClickListener onPositive,
|
||||||
|
final int positiveLabel) {
|
||||||
|
final TextView cancel = body.findViewById(R.id.home_schedule_cancel);
|
||||||
|
final TextView next = body.findViewById(R.id.home_schedule_next);
|
||||||
|
if (cancel != null) {
|
||||||
|
cancel.setOnClickListener(onCancel);
|
||||||
|
}
|
||||||
|
if (next != null) {
|
||||||
|
next.setText(positiveLabel);
|
||||||
|
next.setOnClickListener(onPositive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
package com.android.messaging.ui.home;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
|
import com.android.messaging.R;
|
||||||
|
import com.android.messaging.ui.BugleActionBarActivity;
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetDialog;
|
||||||
|
|
||||||
|
/** File Plus-style security settings: App Lock toggle plus PIN / pattern / password. */
|
||||||
|
public class HomeSecuritySettingsActivity extends BugleActionBarActivity {
|
||||||
|
|
||||||
|
private View mLockRow;
|
||||||
|
private View mPinRow;
|
||||||
|
private View mPatternRow;
|
||||||
|
private View mPasswordRow;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.home_security_settings_activity);
|
||||||
|
if (getSupportActionBar() != null) {
|
||||||
|
getSupportActionBar().hide();
|
||||||
|
}
|
||||||
|
HomeSubHeader.bind(this, R.string.home_security_title, v -> finish());
|
||||||
|
mLockRow = findViewById(R.id.home_security_app_lock);
|
||||||
|
mPinRow = findViewById(R.id.home_security_set_pin);
|
||||||
|
mPatternRow = findViewById(R.id.home_security_set_pattern);
|
||||||
|
mPasswordRow = findViewById(R.id.home_security_set_password);
|
||||||
|
bindRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
bindRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void finish() {
|
||||||
|
super.finish();
|
||||||
|
overridePendingTransition(R.anim.home_activity_hold, R.anim.home_activity_close);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bindRows() {
|
||||||
|
final boolean enabled = HomeAppLock.isEnabled();
|
||||||
|
final HomeAppLock.Method method = HomeAppLock.getMethod();
|
||||||
|
HomeSettingsRow.bind(mLockRow, R.drawable.ic_home_lock,
|
||||||
|
getString(R.string.home_settings_app_lock),
|
||||||
|
getString(R.string.home_settings_app_lock_subtitle),
|
||||||
|
false, true, enabled, null, (button, checked) -> onLockToggled(checked));
|
||||||
|
|
||||||
|
bindMethodRow(mPinRow, R.drawable.ic_home_pin, R.string.home_lock_set_pin,
|
||||||
|
method == HomeAppLock.Method.PIN, enabled, HomeAppLock.MODE_SET_PIN);
|
||||||
|
bindMethodRow(mPatternRow, R.drawable.ic_home_pattern, R.string.home_lock_set_pattern,
|
||||||
|
method == HomeAppLock.Method.PATTERN, enabled, HomeAppLock.MODE_SET_PATTERN);
|
||||||
|
bindMethodRow(mPasswordRow, R.drawable.ic_home_password, R.string.home_lock_set_password,
|
||||||
|
method == HomeAppLock.Method.PASSWORD, enabled, HomeAppLock.MODE_SET_PASSWORD);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bindMethodRow(final View row, final int iconRes, final int titleRes,
|
||||||
|
final boolean current, final boolean enabled, final int mode) {
|
||||||
|
HomeSettingsRow.bind(row, iconRes, getString(titleRes),
|
||||||
|
current ? getString(R.string.home_lock_current_method) : null,
|
||||||
|
true, false, false,
|
||||||
|
v -> startActivity(HomeAppLock.createIntent(this, mode)), null);
|
||||||
|
HomeSettingsRow.setEnabled(row, enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onLockToggled(final boolean checked) {
|
||||||
|
if (checked) {
|
||||||
|
if (HomeAppLock.hasLockConfigured()) {
|
||||||
|
HomeAppLock.setEnabled(true);
|
||||||
|
bindRows();
|
||||||
|
} else {
|
||||||
|
bindRows();
|
||||||
|
showChooseMethodSheet();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
HomeAppLock.setEnabled(false);
|
||||||
|
bindRows();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showChooseMethodSheet() {
|
||||||
|
final LayoutInflater inflater = LayoutInflater.from(this);
|
||||||
|
final LinearLayout content = (LinearLayout) inflater.inflate(
|
||||||
|
R.layout.home_lock_method_sheet, null, false);
|
||||||
|
final View pin = content.findViewById(R.id.home_lock_method_pin);
|
||||||
|
final View pattern = content.findViewById(R.id.home_lock_method_pattern);
|
||||||
|
final View password = content.findViewById(R.id.home_lock_method_password);
|
||||||
|
final int[] selected = { HomeAppLock.MODE_SET_PIN };
|
||||||
|
final BottomSheetDialog[] dialog = new BottomSheetDialog[1];
|
||||||
|
selectMethod(pin, pattern, password, selected[0]);
|
||||||
|
pin.setOnClickListener(v -> {
|
||||||
|
selected[0] = HomeAppLock.MODE_SET_PIN;
|
||||||
|
selectMethod(pin, pattern, password, selected[0]);
|
||||||
|
});
|
||||||
|
pattern.setOnClickListener(v -> {
|
||||||
|
selected[0] = HomeAppLock.MODE_SET_PATTERN;
|
||||||
|
selectMethod(pin, pattern, password, selected[0]);
|
||||||
|
});
|
||||||
|
password.setOnClickListener(v -> {
|
||||||
|
selected[0] = HomeAppLock.MODE_SET_PASSWORD;
|
||||||
|
selectMethod(pin, pattern, password, selected[0]);
|
||||||
|
});
|
||||||
|
content.findViewById(R.id.home_lock_method_continue).setOnClickListener(v -> {
|
||||||
|
if (dialog[0] != null) {
|
||||||
|
dialog[0].dismiss();
|
||||||
|
}
|
||||||
|
startActivity(HomeAppLock.createIntent(this, selected[0]));
|
||||||
|
});
|
||||||
|
dialog[0] = HomeBottomSheet.builder(this)
|
||||||
|
.title(R.string.home_lock_choose_method)
|
||||||
|
.custom(content)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void selectMethod(final View pin, final View pattern, final View password,
|
||||||
|
final int mode) {
|
||||||
|
pin.setSelected(mode == HomeAppLock.MODE_SET_PIN);
|
||||||
|
pattern.setSelected(mode == HomeAppLock.MODE_SET_PATTERN);
|
||||||
|
password.setSelected(mode == HomeAppLock.MODE_SET_PASSWORD);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,6 @@ import android.widget.TextView;
|
|||||||
import androidx.annotation.DrawableRes;
|
import androidx.annotation.DrawableRes;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.widget.SwitchCompat;
|
|
||||||
|
|
||||||
import com.android.messaging.R;
|
import com.android.messaging.R;
|
||||||
|
|
||||||
@@ -28,7 +27,7 @@ public final class HomeSettingsRow {
|
|||||||
final ImageView icon = row.findViewById(R.id.home_settings_row_icon);
|
final ImageView icon = row.findViewById(R.id.home_settings_row_icon);
|
||||||
final TextView titleView = row.findViewById(R.id.home_settings_row_title);
|
final TextView titleView = row.findViewById(R.id.home_settings_row_title);
|
||||||
final TextView subtitleView = row.findViewById(R.id.home_settings_row_subtitle);
|
final TextView subtitleView = row.findViewById(R.id.home_settings_row_subtitle);
|
||||||
final SwitchCompat toggle = row.findViewById(R.id.home_settings_row_switch);
|
final ImageView toggle = row.findViewById(R.id.home_settings_row_switch);
|
||||||
final View chevron = row.findViewById(R.id.home_settings_row_chevron);
|
final View chevron = row.findViewById(R.id.home_settings_row_chevron);
|
||||||
|
|
||||||
icon.setImageResource(iconRes);
|
icon.setImageResource(iconRes);
|
||||||
@@ -48,14 +47,16 @@ public final class HomeSettingsRow {
|
|||||||
chevron.setVisibility(showChevron ? View.VISIBLE : View.GONE);
|
chevron.setVisibility(showChevron ? View.VISIBLE : View.GONE);
|
||||||
}
|
}
|
||||||
if (toggle != null) {
|
if (toggle != null) {
|
||||||
toggle.setOnCheckedChangeListener(null);
|
|
||||||
toggle.setVisibility(showSwitch ? View.VISIBLE : View.GONE);
|
toggle.setVisibility(showSwitch ? View.VISIBLE : View.GONE);
|
||||||
toggle.setChecked(switchOn);
|
toggle.setSelected(switchOn);
|
||||||
toggle.setOnCheckedChangeListener(switchListener);
|
|
||||||
}
|
}
|
||||||
row.setOnClickListener(v -> {
|
row.setOnClickListener(v -> {
|
||||||
if (showSwitch && toggle != null) {
|
if (showSwitch && toggle != null) {
|
||||||
toggle.toggle();
|
final boolean checked = !toggle.isSelected();
|
||||||
|
toggle.setSelected(checked);
|
||||||
|
if (switchListener != null) {
|
||||||
|
switchListener.onCheckedChanged(null, checked);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (clickListener != null) {
|
if (clickListener != null) {
|
||||||
clickListener.onClick(v);
|
clickListener.onClick(v);
|
||||||
@@ -98,7 +99,7 @@ public final class HomeSettingsRow {
|
|||||||
public static void setEnabled(@NonNull final View row, final boolean enabled) {
|
public static void setEnabled(@NonNull final View row, final boolean enabled) {
|
||||||
row.setEnabled(enabled);
|
row.setEnabled(enabled);
|
||||||
row.setAlpha(enabled ? 1f : 0.4f);
|
row.setAlpha(enabled ? 1f : 0.4f);
|
||||||
final SwitchCompat toggle = row.findViewById(R.id.home_settings_row_switch);
|
final View toggle = row.findViewById(R.id.home_settings_row_switch);
|
||||||
if (toggle != null) {
|
if (toggle != null) {
|
||||||
toggle.setEnabled(enabled);
|
toggle.setEnabled(enabled);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,8 @@
|
|||||||
package com.android.messaging.util;
|
package com.android.messaging.util;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
|
import android.accounts.AccountManager;
|
||||||
|
import android.accounts.AuthenticatorDescription;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.ContentUris;
|
import android.content.ContentUris;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@@ -33,11 +35,18 @@ import android.provider.ContactsContract.Directory;
|
|||||||
import android.provider.ContactsContract.DisplayNameSources;
|
import android.provider.ContactsContract.DisplayNameSources;
|
||||||
import android.provider.ContactsContract.PhoneLookup;
|
import android.provider.ContactsContract.PhoneLookup;
|
||||||
import android.provider.ContactsContract.Profile;
|
import android.provider.ContactsContract.Profile;
|
||||||
|
import android.provider.ContactsContract.RawContacts;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.content.ActivityNotFoundException;
|
import android.content.ActivityNotFoundException;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import com.android.ex.chips.RecipientEntry;
|
import com.android.ex.chips.RecipientEntry;
|
||||||
import com.android.messaging.datamodel.CursorQueryData;
|
import com.android.messaging.datamodel.CursorQueryData;
|
||||||
import com.android.messaging.datamodel.FrequentContactsCursorQueryData;
|
import com.android.messaging.datamodel.FrequentContactsCursorQueryData;
|
||||||
@@ -361,6 +370,13 @@ public class ContactUtil {
|
|||||||
* In that case, each phone will be returned as a separate record in the result cursor.
|
* In that case, each phone will be returned as a separate record in the result cursor.
|
||||||
*/
|
*/
|
||||||
public static CursorQueryData getPhones(final Context context) {
|
public static CursorQueryData getPhones(final Context context) {
|
||||||
|
return getPhones(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as {@link #getPhones(Context)} but limited to one account, or device-only contacts.
|
||||||
|
*/
|
||||||
|
public static CursorQueryData getPhones(final Context context, final AccountFilter filter) {
|
||||||
if (!ContactUtil.hasReadContactsPermission()) {
|
if (!ContactUtil.hasReadContactsPermission()) {
|
||||||
return CursorQueryData.getEmptyQueryData();
|
return CursorQueryData.getEmptyQueryData();
|
||||||
}
|
}
|
||||||
@@ -373,8 +389,173 @@ public class ContactUtil {
|
|||||||
.appendQueryParameter(Contacts.EXTRA_ADDRESS_BOOK_INDEX, "true")
|
.appendQueryParameter(Contacts.EXTRA_ADDRESS_BOOK_INDEX, "true")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
return new CursorQueryData(context, uri, PhoneQuery.PROJECTION, null, null,
|
return new CursorQueryData(context, uri, PhoneQuery.PROJECTION,
|
||||||
PhoneQuery.SORT_KEY);
|
accountSelection(filter), accountSelectionArgs(filter), PhoneQuery.SORT_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Restricts a phone/email query to one contacts account. Null means every account. */
|
||||||
|
public static final class AccountFilter {
|
||||||
|
public final String accountName;
|
||||||
|
public final String accountType;
|
||||||
|
public final boolean deviceOnly;
|
||||||
|
|
||||||
|
private AccountFilter(final String accountName, final String accountType,
|
||||||
|
final boolean deviceOnly) {
|
||||||
|
this.accountName = accountName;
|
||||||
|
this.accountType = accountType;
|
||||||
|
this.deviceOnly = deviceOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AccountFilter account(final String accountName, final String accountType) {
|
||||||
|
if (TextUtils.isEmpty(accountName) && TextUtils.isEmpty(accountType)) {
|
||||||
|
return device();
|
||||||
|
}
|
||||||
|
return new AccountFilter(accountName, accountType == null ? "" : accountType, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AccountFilter device() {
|
||||||
|
return new AccountFilter(null, null, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean matches(final AccountFilter other) {
|
||||||
|
if (other == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return deviceOnly == other.deviceOnly
|
||||||
|
&& TextUtils.equals(accountName, other.accountName)
|
||||||
|
&& TextUtils.equals(accountType, other.accountType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** One source shown in the compose "All contacts" sheet. */
|
||||||
|
public static final class ContactAccount {
|
||||||
|
public final String accountName;
|
||||||
|
public final String accountType;
|
||||||
|
public final String title;
|
||||||
|
public final String subtitle;
|
||||||
|
public final int contactCount;
|
||||||
|
public final boolean deviceOnly;
|
||||||
|
|
||||||
|
ContactAccount(final String accountName, final String accountType, final String title,
|
||||||
|
final String subtitle, final int contactCount, final boolean deviceOnly) {
|
||||||
|
this.accountName = accountName;
|
||||||
|
this.accountType = accountType;
|
||||||
|
this.title = title;
|
||||||
|
this.subtitle = subtitle;
|
||||||
|
this.contactCount = contactCount;
|
||||||
|
this.deviceOnly = deviceOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccountFilter toFilter() {
|
||||||
|
return deviceOnly ? AccountFilter.device() : AccountFilter.account(accountName,
|
||||||
|
accountType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String accountSelection(final AccountFilter filter) {
|
||||||
|
if (filter == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (filter.deviceOnly) {
|
||||||
|
return "(" + RawContacts.ACCOUNT_NAME + " IS NULL OR " + RawContacts.ACCOUNT_NAME
|
||||||
|
+ "='') AND (" + RawContacts.ACCOUNT_TYPE + " IS NULL OR "
|
||||||
|
+ RawContacts.ACCOUNT_TYPE + "='')";
|
||||||
|
}
|
||||||
|
return RawContacts.ACCOUNT_NAME + "=? AND " + RawContacts.ACCOUNT_TYPE + "=?";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String[] accountSelectionArgs(final AccountFilter filter) {
|
||||||
|
if (filter == null || filter.deviceOnly) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new String[] { filter.accountName, filter.accountType };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accounts that currently have at least one raw contact, for the compose source sheet.
|
||||||
|
*/
|
||||||
|
public static List<ContactAccount> getContactAccounts(final Context context) {
|
||||||
|
final List<ContactAccount> accounts = new ArrayList<>();
|
||||||
|
if (!hasReadContactsPermission() || context == null) {
|
||||||
|
return accounts;
|
||||||
|
}
|
||||||
|
final Map<String, AccountBucket> buckets = new LinkedHashMap<>();
|
||||||
|
Cursor cursor = null;
|
||||||
|
try {
|
||||||
|
cursor = context.getContentResolver().query(RawContacts.CONTENT_URI,
|
||||||
|
new String[] {
|
||||||
|
RawContacts.ACCOUNT_NAME,
|
||||||
|
RawContacts.ACCOUNT_TYPE,
|
||||||
|
RawContacts.CONTACT_ID
|
||||||
|
},
|
||||||
|
RawContacts.DELETED + "=0",
|
||||||
|
null,
|
||||||
|
RawContacts.ACCOUNT_NAME);
|
||||||
|
if (cursor != null) {
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
final String name = cursor.getString(0);
|
||||||
|
final String type = cursor.getString(1);
|
||||||
|
final long contactId = cursor.getLong(2);
|
||||||
|
final boolean device = TextUtils.isEmpty(name) && TextUtils.isEmpty(type);
|
||||||
|
final String key = device ? "device" : name + "\0" + type;
|
||||||
|
AccountBucket bucket = buckets.get(key);
|
||||||
|
if (bucket == null) {
|
||||||
|
bucket = new AccountBucket(name, type, device);
|
||||||
|
buckets.put(key, bucket);
|
||||||
|
}
|
||||||
|
bucket.contactIds.add(contactId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (final SecurityException ignored) {
|
||||||
|
return accounts;
|
||||||
|
} finally {
|
||||||
|
if (cursor != null) {
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (final AccountBucket bucket : buckets.values()) {
|
||||||
|
accounts.add(new ContactAccount(bucket.accountName, bucket.accountType,
|
||||||
|
bucket.deviceOnly ? null : bucket.accountName,
|
||||||
|
authenticatorLabel(context, bucket.accountType, bucket.deviceOnly),
|
||||||
|
bucket.contactIds.size(), bucket.deviceOnly));
|
||||||
|
}
|
||||||
|
return accounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class AccountBucket {
|
||||||
|
final String accountName;
|
||||||
|
final String accountType;
|
||||||
|
final boolean deviceOnly;
|
||||||
|
final HashSet<Long> contactIds = new HashSet<>();
|
||||||
|
|
||||||
|
AccountBucket(final String accountName, final String accountType,
|
||||||
|
final boolean deviceOnly) {
|
||||||
|
this.accountName = accountName;
|
||||||
|
this.accountType = accountType;
|
||||||
|
this.deviceOnly = deviceOnly;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String authenticatorLabel(final Context context, final String accountType,
|
||||||
|
final boolean deviceOnly) {
|
||||||
|
if (deviceOnly || TextUtils.isEmpty(accountType)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
final AuthenticatorDescription[] types =
|
||||||
|
AccountManager.get(context).getAuthenticatorTypes();
|
||||||
|
for (final AuthenticatorDescription description : types) {
|
||||||
|
if (TextUtils.equals(description.type, accountType)) {
|
||||||
|
final CharSequence label = context.getPackageManager().getText(
|
||||||
|
description.packageName, description.labelId, null);
|
||||||
|
if (!TextUtils.isEmpty(label)) {
|
||||||
|
return label.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (final RuntimeException ignored) {
|
||||||
|
}
|
||||||
|
return accountType;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -403,10 +584,15 @@ public class ContactUtil {
|
|||||||
*/
|
*/
|
||||||
public static CursorQueryData filterDestination(final Context context,
|
public static CursorQueryData filterDestination(final Context context,
|
||||||
final String destination) {
|
final String destination) {
|
||||||
|
return filterDestination(context, destination, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CursorQueryData filterDestination(final Context context,
|
||||||
|
final String destination, final AccountFilter filter) {
|
||||||
if (shouldFilterForEmail(destination)) {
|
if (shouldFilterForEmail(destination)) {
|
||||||
return ContactUtil.filterEmails(context, destination);
|
return ContactUtil.filterEmails(context, destination, filter);
|
||||||
} else {
|
} else {
|
||||||
return ContactUtil.filterPhones(context, destination);
|
return ContactUtil.filterPhones(context, destination, filter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,7 +616,13 @@ public class ContactUtil {
|
|||||||
* we support email addresses as well.
|
* we support email addresses as well.
|
||||||
*/
|
*/
|
||||||
public static CursorQueryData filterPhones(final Context context, final String query) {
|
public static CursorQueryData filterPhones(final Context context, final String query) {
|
||||||
return filterPhonesInternal(context, Phone.CONTENT_FILTER_URI, query, Directory.DEFAULT);
|
return filterPhones(context, query, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CursorQueryData filterPhones(final Context context, final String query,
|
||||||
|
final AccountFilter filter) {
|
||||||
|
return filterPhonesInternal(context, Phone.CONTENT_FILTER_URI, query, Directory.DEFAULT,
|
||||||
|
filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -439,18 +631,19 @@ public class ContactUtil {
|
|||||||
public static CursorQueryData filterPhonesEnterprise(final Context context,
|
public static CursorQueryData filterPhonesEnterprise(final Context context,
|
||||||
final String query) {
|
final String query) {
|
||||||
return filterPhonesInternal(context, Phone.ENTERPRISE_CONTENT_FILTER_URI, query,
|
return filterPhonesInternal(context, Phone.ENTERPRISE_CONTENT_FILTER_URI, query,
|
||||||
Directory.ENTERPRISE_DEFAULT);
|
Directory.ENTERPRISE_DEFAULT, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CursorQueryData filterPhonesInternal(final Context context,
|
private static CursorQueryData filterPhonesInternal(final Context context,
|
||||||
final Uri phoneFilterBaseUri, final String query, final long directoryId) {
|
final Uri phoneFilterBaseUri, final String query, final long directoryId,
|
||||||
|
final AccountFilter filter) {
|
||||||
if (!ContactUtil.hasReadContactsPermission()) {
|
if (!ContactUtil.hasReadContactsPermission()) {
|
||||||
return CursorQueryData.getEmptyQueryData();
|
return CursorQueryData.getEmptyQueryData();
|
||||||
}
|
}
|
||||||
Uri phoneFilterUri = buildDirectorySearchUri(phoneFilterBaseUri, query, directoryId);
|
Uri phoneFilterUri = buildDirectorySearchUri(phoneFilterBaseUri, query, directoryId);
|
||||||
return new CursorQueryData(context,
|
return new CursorQueryData(context,
|
||||||
phoneFilterUri,
|
phoneFilterUri,
|
||||||
PhoneQuery.PROJECTION, null, null,
|
PhoneQuery.PROJECTION, accountSelection(filter), accountSelectionArgs(filter),
|
||||||
PhoneQuery.SORT_KEY);
|
PhoneQuery.SORT_KEY);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -492,7 +685,13 @@ public class ContactUtil {
|
|||||||
* we support email addresses as well.
|
* we support email addresses as well.
|
||||||
*/
|
*/
|
||||||
public static CursorQueryData filterEmails(final Context context, final String query) {
|
public static CursorQueryData filterEmails(final Context context, final String query) {
|
||||||
return filterEmailsInternal(context, Email.CONTENT_FILTER_URI, query, Directory.DEFAULT);
|
return filterEmails(context, query, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CursorQueryData filterEmails(final Context context, final String query,
|
||||||
|
final AccountFilter filter) {
|
||||||
|
return filterEmailsInternal(context, Email.CONTENT_FILTER_URI, query, Directory.DEFAULT,
|
||||||
|
filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -501,11 +700,12 @@ public class ContactUtil {
|
|||||||
public static CursorQueryData filterEmailsEnterprise(final Context context,
|
public static CursorQueryData filterEmailsEnterprise(final Context context,
|
||||||
final String query) {
|
final String query) {
|
||||||
return filterEmailsInternal(context, Email.ENTERPRISE_CONTENT_FILTER_URI, query,
|
return filterEmailsInternal(context, Email.ENTERPRISE_CONTENT_FILTER_URI, query,
|
||||||
Directory.ENTERPRISE_DEFAULT);
|
Directory.ENTERPRISE_DEFAULT, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CursorQueryData filterEmailsInternal(final Context context,
|
private static CursorQueryData filterEmailsInternal(final Context context,
|
||||||
final Uri filterEmailsBaseUri, final String query, final long directoryId) {
|
final Uri filterEmailsBaseUri, final String query, final long directoryId,
|
||||||
|
final AccountFilter filter) {
|
||||||
if (!ContactUtil.hasReadContactsPermission()) {
|
if (!ContactUtil.hasReadContactsPermission()) {
|
||||||
return CursorQueryData.getEmptyQueryData();
|
return CursorQueryData.getEmptyQueryData();
|
||||||
}
|
}
|
||||||
@@ -513,7 +713,7 @@ public class ContactUtil {
|
|||||||
directoryId);
|
directoryId);
|
||||||
return new CursorQueryData(context,
|
return new CursorQueryData(context,
|
||||||
filterEmailsUri,
|
filterEmailsUri,
|
||||||
PhoneQuery.PROJECTION, null, null,
|
PhoneQuery.PROJECTION, accountSelection(filter), accountSelectionArgs(filter),
|
||||||
PhoneQuery.SORT_KEY);
|
PhoneQuery.SORT_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -116,7 +116,15 @@ public class OsUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean hasLocationPermission() {
|
public static boolean hasLocationPermission() {
|
||||||
return OsUtil.hasPermission(Manifest.permission.ACCESS_FINE_LOCATION);
|
return hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)
|
||||||
|
|| hasPermission(Manifest.permission.ACCESS_COARSE_LOCATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String[] getLocationPermissions() {
|
||||||
|
return new String[] {
|
||||||
|
Manifest.permission.ACCESS_FINE_LOCATION,
|
||||||
|
Manifest.permission.ACCESS_COARSE_LOCATION,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean hasRecordAudioPermission() {
|
public static boolean hasRecordAudioPermission() {
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:color="@color/home_surface_elevated">
|
||||||
|
<item>
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<corners android:radius="@dimen/looply_radius_pill" />
|
||||||
|
<solid android:color="@color/home_chip_bg" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</ripple>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:color="@color/home_accent_soft">
|
||||||
|
<item>
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<corners android:radius="@dimen/looply_radius_md" />
|
||||||
|
<solid android:color="@color/home_search_field_bg" />
|
||||||
|
<stroke
|
||||||
|
android:width="1.5dp"
|
||||||
|
android:color="@color/home_divider" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</ripple>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:color="@color/home_accent">
|
||||||
|
<item>
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<solid android:color="@color/home_accent_soft" />
|
||||||
|
<corners android:radius="@dimen/looply_radius_pill" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</ripple>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:color="@color/home_surface_elevated">
|
||||||
|
<item>
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<solid android:color="@color/home_row_bg" />
|
||||||
|
<corners
|
||||||
|
android:bottomLeftRadius="@dimen/looply_radius_md"
|
||||||
|
android:bottomRightRadius="@dimen/looply_radius_md" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</ripple>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:color="@color/home_surface_elevated">
|
||||||
|
<item>
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<solid android:color="@color/home_row_bg" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</ripple>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:color="@color/home_surface_elevated">
|
||||||
|
<item>
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<solid android:color="@color/home_row_bg" />
|
||||||
|
<corners android:radius="@dimen/looply_radius_md" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</ripple>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:color="@color/home_surface_elevated">
|
||||||
|
<item>
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<solid android:color="@color/home_row_bg" />
|
||||||
|
<corners
|
||||||
|
android:topLeftRadius="@dimen/looply_radius_md"
|
||||||
|
android:topRightRadius="@dimen/looply_radius_md" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</ripple>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<gradient
|
||||||
|
android:angle="270"
|
||||||
|
android:endColor="#99F4F1FB"
|
||||||
|
android:startColor="#00F4F1FB"
|
||||||
|
android:type="linear" />
|
||||||
|
</shape>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<gradient
|
||||||
|
android:angle="270"
|
||||||
|
android:centerColor="#4DF4F1FB"
|
||||||
|
android:endColor="#00F4F1FB"
|
||||||
|
android:startColor="@color/home_bg"
|
||||||
|
android:type="linear" />
|
||||||
|
</shape>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="@color/home_accent" />
|
||||||
|
<corners android:radius="26dp" />
|
||||||
|
</shape>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:state_selected="true">
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<solid android:color="@color/home_search_field_bg" />
|
||||||
|
<corners android:radius="@dimen/looply_radius_md" />
|
||||||
|
<stroke
|
||||||
|
android:width="2dp"
|
||||||
|
android:color="@color/home_accent" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<solid android:color="@color/home_search_field_bg" />
|
||||||
|
<corners android:radius="@dimen/looply_radius_md" />
|
||||||
|
<stroke
|
||||||
|
android:width="1.5dp"
|
||||||
|
android:color="@color/home_divider" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</selector>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:color="@color/home_surface_elevated">
|
||||||
|
<item>
|
||||||
|
<shape android:shape="oval">
|
||||||
|
<solid android:color="@color/home_row_bg" />
|
||||||
|
<stroke
|
||||||
|
android:width="1dp"
|
||||||
|
android:color="@color/home_button_stroke" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</ripple>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:state_selected="true">
|
||||||
|
<shape android:shape="oval">
|
||||||
|
<solid android:color="@color/home_accent" />
|
||||||
|
<size android:width="14dp" android:height="14dp" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<shape android:shape="oval">
|
||||||
|
<solid android:color="@android:color/transparent" />
|
||||||
|
<stroke
|
||||||
|
android:width="2dp"
|
||||||
|
android:color="@color/home_divider" />
|
||||||
|
<size android:width="14dp" android:height="14dp" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</selector>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:color="@color/home_accent">
|
||||||
|
<item>
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<solid android:color="@color/home_unknown_sender_banner_bg" />
|
||||||
|
<corners android:radius="@dimen/looply_radius_pill" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</ripple>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@drawable/ic_home_switch_on" android:state_selected="true" />
|
||||||
|
<item android:drawable="@drawable/ic_home_switch_off" />
|
||||||
|
</selector>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/transparent"
|
||||||
|
android:pathData="M8.5,6h11A2.5,2.5 0,0 1,22 8.5v7A2.5,2.5 0,0 1,19.5 18h-11L3,12z"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:strokeColor="#000000"
|
||||||
|
android:strokeLineJoin="round" />
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/transparent"
|
||||||
|
android:pathData="M11,9.5l5,5M16,9.5l-5,5"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:strokeColor="#000000"
|
||||||
|
android:strokeLineCap="round" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="16dp"
|
||||||
|
android:height="16dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#8E8E93"
|
||||||
|
android:pathData="M7.41,8.59L12,13.17l4.59,-4.58L18,10l-6,6 -6,-6 1.41,-1.41z" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM4,12c0,-0.61 0.08,-1.21 0.21,-1.78L8,14.5V16c0,1.1 0.9,2 2,2v1.93C6.47,19.16 4,15.91 4,12zM16.9,18.42V17c0,-0.55 -0.45,-1 -1,-1h-1v-3c0,-0.55 -0.45,-1 -1,-1h-3v-2h2c0.55,0 1,-0.45 1,-1V8h2c1.1,0 2,-0.9 2,-2V5.59C18.87,6.88 20,9.3 20,12c0,3.35 -2.08,6.22 -5.03,7.42l1.93,-1z" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/transparent"
|
||||||
|
android:pathData="M7,11V8a5,5 0,0 1,10 0v3"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:strokeColor="#000000"
|
||||||
|
android:strokeLineCap="round" />
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/transparent"
|
||||||
|
android:pathData="M6,11h12a2,2 0,0 1,2 2v7a2,2 0,0 1,-2 2H6a2,2 0,0 1,-2 -2v-7a2,2 0,0 1,2 -2z"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:strokeColor="#000000"
|
||||||
|
android:strokeLineJoin="round" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/transparent"
|
||||||
|
android:pathData="M5,11h14a2,2 0,0 1,2 2v6a2,2 0,0 1,-2 2H5a2,2 0,0 1,-2 -2v-6a2,2 0,0 1,2 -2z"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:strokeColor="#000000"
|
||||||
|
android:strokeLineJoin="round" />
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/transparent"
|
||||||
|
android:pathData="M8,11V8a4,4 0,0 1,8 0v3"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:strokeColor="#000000"
|
||||||
|
android:strokeLineCap="round" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:pathData="M6,6m-1.6,0a1.6,1.6 0,1 1,3.2 0a1.6,1.6 0,1 1,-3.2 0" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:pathData="M12,6m-1.6,0a1.6,1.6 0,1 1,3.2 0a1.6,1.6 0,1 1,-3.2 0" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:pathData="M18,6m-1.6,0a1.6,1.6 0,1 1,3.2 0a1.6,1.6 0,1 1,-3.2 0" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:pathData="M6,12m-1.6,0a1.6,1.6 0,1 1,3.2 0a1.6,1.6 0,1 1,-3.2 0" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:pathData="M12,12m-1.6,0a1.6,1.6 0,1 1,3.2 0a1.6,1.6 0,1 1,-3.2 0" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:pathData="M18,12m-1.6,0a1.6,1.6 0,1 1,3.2 0a1.6,1.6 0,1 1,-3.2 0" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:pathData="M6,18m-1.6,0a1.6,1.6 0,1 1,3.2 0a1.6,1.6 0,1 1,-3.2 0" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:pathData="M12,18m-1.6,0a1.6,1.6 0,1 1,3.2 0a1.6,1.6 0,1 1,-3.2 0" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:pathData="M18,18m-1.6,0a1.6,1.6 0,1 1,3.2 0a1.6,1.6 0,1 1,-3.2 0" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/transparent"
|
||||||
|
android:pathData="M4,8h16M4,12h16M4,16h10"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:strokeColor="#000000"
|
||||||
|
android:strokeLineCap="round" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="14dp"
|
||||||
|
android:height="14dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="52dp"
|
||||||
|
android:height="32dp"
|
||||||
|
android:viewportWidth="52"
|
||||||
|
android:viewportHeight="32">
|
||||||
|
<path
|
||||||
|
android:fillColor="@color/home_switch_off"
|
||||||
|
android:fillType="evenOdd"
|
||||||
|
android:pathData="M16,0H36A16,16 0,0 1,36 32H16A16,16 0,0 1,16 0ZM16,16m-13,0a13,13 0,1 1,26 0a13,13 0,1 1,-26 0" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="52dp"
|
||||||
|
android:height="32dp"
|
||||||
|
android:viewportWidth="52"
|
||||||
|
android:viewportHeight="32">
|
||||||
|
<path
|
||||||
|
android:fillColor="@color/home_switch_on"
|
||||||
|
android:fillType="evenOdd"
|
||||||
|
android:pathData="M16,0H36A16,16 0,0 1,36 32H16A16,16 0,0 1,16 0ZM36,16m-13,0a13,13 0,1 1,26 0a13,13 0,1 1,-26 0" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/transparent"
|
||||||
|
android:pathData="M2.5,12S6,6.5 12,6.5 21.5,12 21.5,12 18,17.5 12,17.5 2.5,12 2.5,12z"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:strokeColor="#000000"
|
||||||
|
android:strokeLineJoin="round" />
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/transparent"
|
||||||
|
android:pathData="M12,12m-2.5,0a2.5,2.5 0,1 1,5 0a2.5,2.5 0,1 1,-5 0"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:strokeColor="#000000" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/transparent"
|
||||||
|
android:pathData="M3,4.5L20.5,22"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:strokeColor="#000000"
|
||||||
|
android:strokeLineCap="round" />
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/transparent"
|
||||||
|
android:pathData="M9.2,9.4C8.2,10.1 7.5,11.1 7.5,12.4a4.5,4.5 0,0 0,7.1 3.7"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:strokeColor="#000000"
|
||||||
|
android:strokeLineCap="round" />
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/transparent"
|
||||||
|
android:pathData="M4,12s2.6,-4.2 8,-4.2c1.1,0 2.1,0.2 3,0.5M20,12s-2.6,4.2 -8,4.2c-0.8,0 -1.5,-0.1 -2.2,-0.3"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:strokeColor="#000000"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round" />
|
||||||
|
</vector>
|
||||||
@@ -188,6 +188,49 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/home_schedule_chip_row"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="start"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="@dimen/looply_space_md"
|
||||||
|
android:paddingTop="@dimen/looply_space_sm"
|
||||||
|
android:paddingEnd="@dimen/looply_space_md"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/home_schedule_chip"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:background="@drawable/home_bg_schedule_chip"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="4dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/home_schedule_chip_text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:textColor="@color/home_unknown_sender_accent"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/home_schedule_chip_clear"
|
||||||
|
android:layout_width="36dp"
|
||||||
|
android:layout_height="36dp"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@string/home_schedule_clear"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:src="@drawable/ic_home_close"
|
||||||
|
app:tint="@color/home_unknown_sender_accent" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<!-- Google Messages–style compose row: pill (+ | text | emoji | gallery) + voice/send -->
|
<!-- Google Messages–style compose row: pill (+ | text | emoji | gallery) + voice/send -->
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
|||||||
@@ -15,9 +15,16 @@
|
|||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
<LinearLayout
|
<FrameLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/home_bg"
|
||||||
|
android:clipChildren="false"
|
||||||
|
android:clipToPadding="false">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="@color/home_bg"
|
android:background="@color/home_bg"
|
||||||
@@ -197,14 +204,23 @@
|
|||||||
app:srcCompat="@drawable/ic_checkmark_circle_primary" />
|
app:srcCompat="@drawable/ic_checkmark_circle_primary" />
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/home_category_collapse"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/home_bg"
|
||||||
|
android:clipChildren="true"
|
||||||
|
android:clipToPadding="true">
|
||||||
|
|
||||||
<com.android.messaging.ui.home.HomeCategoryStrip
|
<com.android.messaging.ui.home.HomeCategoryStrip
|
||||||
android:id="@+id/home_category_strip"
|
android:id="@+id/home_category_strip"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="top"
|
||||||
android:paddingStart="@dimen/looply_screen_padding"
|
android:paddingStart="@dimen/looply_screen_padding"
|
||||||
android:paddingTop="@dimen/looply_space_md"
|
android:paddingTop="@dimen/looply_space_sm"
|
||||||
android:paddingEnd="12dp"
|
android:paddingEnd="12dp"
|
||||||
android:paddingBottom="@dimen/looply_space_md">
|
android:paddingBottom="2dp">
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/home_category_all"
|
android:id="@+id/home_category_all"
|
||||||
@@ -263,56 +279,20 @@
|
|||||||
android:layout_height="32dp" />
|
android:layout_height="32dp" />
|
||||||
</com.android.messaging.ui.home.HomeCategoryStrip>
|
</com.android.messaging.ui.home.HomeCategoryStrip>
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:id="@+id/home_messages_host"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:clipChildren="true"
|
|
||||||
android:clipToPadding="false">
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@android:id/list"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:background="@color/home_bg"
|
|
||||||
android:clipToPadding="false"
|
|
||||||
android:dividerHeight="0px"
|
|
||||||
android:listSelector="@android:color/transparent"
|
|
||||||
android:paddingBottom="104dp" />
|
|
||||||
|
|
||||||
<include
|
|
||||||
android:id="@+id/no_conversations_view"
|
|
||||||
layout="@layout/home_empty_state"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
<include
|
|
||||||
android:id="@+id/home_automation_pane"
|
|
||||||
layout="@layout/home_automation_pane"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent" />
|
|
||||||
|
|
||||||
<include layout="@layout/home_list_profile_popup" />
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/home_search_bar_container"
|
android:id="@+id/home_search_bar_container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="top"
|
android:layout_gravity="top"
|
||||||
android:background="@drawable/home_bg_search_bar_outer"
|
android:background="@color/home_bg"
|
||||||
android:clipChildren="false"
|
android:clipChildren="false"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:elevation="0dp"
|
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingStart="12dp"
|
android:paddingStart="12dp"
|
||||||
android:paddingTop="6dp"
|
android:paddingTop="4dp"
|
||||||
android:paddingEnd="12dp"
|
android:paddingEnd="12dp"
|
||||||
android:paddingBottom="10dp"
|
android:paddingBottom="4dp"
|
||||||
android:visibility="gone">
|
android:visibility="invisible">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/home_search_bar"
|
android:id="@+id/home_search_bar"
|
||||||
@@ -355,6 +335,64 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/home_messages_host"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:clipChildren="false"
|
||||||
|
android:clipToPadding="false">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@android:id/list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/home_bg"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:dividerHeight="0px"
|
||||||
|
android:listSelector="@android:color/transparent"
|
||||||
|
android:paddingBottom="104dp" />
|
||||||
|
|
||||||
|
<com.android.messaging.ui.home.HomePassThroughView
|
||||||
|
android:id="@+id/home_list_top_fade"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/home_list_top_fade_height"
|
||||||
|
android:layout_gravity="top"
|
||||||
|
android:background="@drawable/home_bg_list_top_fade"
|
||||||
|
android:clickable="false"
|
||||||
|
android:focusable="false"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<com.android.messaging.ui.home.HomePassThroughView
|
||||||
|
android:id="@+id/home_list_bottom_fade"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/home_list_bottom_fade_height"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:layout_marginBottom="@dimen/home_list_bottom_fade_margin"
|
||||||
|
android:background="@drawable/home_bg_list_bottom_fade"
|
||||||
|
android:clickable="false"
|
||||||
|
android:focusable="false"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/no_conversations_view"
|
||||||
|
layout="@layout/home_empty_state"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/home_automation_pane"
|
||||||
|
layout="@layout/home_automation_pane"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/home_selection_bar"
|
android:id="@+id/home_selection_bar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -525,3 +563,9 @@
|
|||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<include
|
||||||
|
layout="@layout/home_list_profile_popup"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
</FrameLayout>
|
||||||
|
|||||||
@@ -107,7 +107,7 @@
|
|||||||
android:minHeight="72dp"
|
android:minHeight="72dp"
|
||||||
android:paddingStart="@dimen/looply_space_md"
|
android:paddingStart="@dimen/looply_space_md"
|
||||||
android:paddingEnd="@dimen/looply_space_lg"
|
android:paddingEnd="@dimen/looply_space_lg"
|
||||||
android:paddingTop="@dimen/looply_space_md"
|
android:paddingTop="@dimen/looply_space_sm"
|
||||||
android:paddingBottom="@dimen/looply_space_md"
|
android:paddingBottom="@dimen/looply_space_md"
|
||||||
android:baselineAligned="false">
|
android:baselineAligned="false">
|
||||||
|
|
||||||
|
|||||||
@@ -127,72 +127,79 @@
|
|||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
android:importantForAccessibility="no" />
|
android:importantForAccessibility="no" />
|
||||||
|
|
||||||
<com.android.messaging.ui.LineWrapLayout
|
|
||||||
android:id="@+id/message_metadata"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:importantForAccessibility="noHideDescendants" >
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/message_sender_name"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginRight="4dp"
|
|
||||||
style="@style/ConversationMessageStatus"
|
|
||||||
android:visibility="gone" />
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/message_status"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
style="@style/ConversationMessageStatus"
|
|
||||||
android:visibility="gone" />
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/sim_name"
|
|
||||||
style="@style/MessageSimIndicator"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:visibility="gone" />
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/smsDeliveredBadge"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
style="@style/SmsDeliverdBadge"
|
|
||||||
android:src="@drawable/ic_sms_delivery_ok"
|
|
||||||
android:visibility="gone" />
|
|
||||||
</com.android.messaging.ui.LineWrapLayout>
|
|
||||||
|
|
||||||
</com.android.messaging.ui.conversation.MessageBubbleBackground>
|
</com.android.messaging.ui.conversation.MessageBubbleBackground>
|
||||||
|
|
||||||
<LinearLayout
|
<com.android.messaging.ui.LineWrapLayout
|
||||||
android:id="@+id/message_send_error"
|
android:id="@+id/message_metadata"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="6dp"
|
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:orientation="horizontal"
|
android:importantForAccessibility="noHideDescendants"
|
||||||
android:visibility="gone">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="14dp"
|
|
||||||
android:layout_height="14dp"
|
|
||||||
android:contentDescription="@null"
|
|
||||||
android:importantForAccessibility="no"
|
|
||||||
android:scaleType="centerInside"
|
|
||||||
android:src="@drawable/ic_message_send_error" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/message_send_error_text"
|
android:id="@+id/message_sender_name"
|
||||||
|
style="@style/ConversationMessageStatus"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="4dp"
|
android:layout_marginEnd="4dp"
|
||||||
android:includeFontPadding="false"
|
android:includeFontPadding="false"
|
||||||
android:textColor="@color/home_destructive"
|
android:visibility="gone" />
|
||||||
android:textSize="12sp" />
|
|
||||||
</LinearLayout>
|
<TextView
|
||||||
|
android:id="@+id/message_status"
|
||||||
|
style="@style/ConversationMessageStatus"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/message_status_action"
|
||||||
|
style="@style/ConversationMessageStatus"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:paddingStart="2dp"
|
||||||
|
android:paddingTop="1dp"
|
||||||
|
android:paddingEnd="2dp"
|
||||||
|
android:paddingBottom="1dp"
|
||||||
|
android:text="@string/home_message_status_check_options"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/message_status_icon"
|
||||||
|
android:layout_width="14dp"
|
||||||
|
android:layout_height="14dp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:clickable="true"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:focusable="true"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:scaleType="centerInside"
|
||||||
|
android:src="@drawable/ic_home_status_retry"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sim_name"
|
||||||
|
style="@style/MessageSimIndicator"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/smsDeliveredBadge"
|
||||||
|
style="@style/SmsDeliverdBadge"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:src="@drawable/ic_sms_delivery_ok"
|
||||||
|
android:visibility="gone" />
|
||||||
|
</com.android.messaging.ui.LineWrapLayout>
|
||||||
|
|
||||||
</com.android.messaging.ui.conversation.ConversationMessageBubbleView>
|
</com.android.messaging.ui.conversation.ConversationMessageBubbleView>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/home_bg_compose_group"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:minHeight="@dimen/looply_button_height"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="@dimen/looply_space_lg"
|
||||||
|
android:paddingEnd="@dimen/looply_space_lg">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/home_compose_account_icon"
|
||||||
|
android:layout_width="22dp"
|
||||||
|
android:layout_height="22dp"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:src="@drawable/ic_home_menu_person"
|
||||||
|
android:tint="@color/home_text_primary" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/looply_space_md"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/home_compose_account_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="@color/home_text_primary"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/home_compose_account_subtitle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="@color/home_text_secondary"
|
||||||
|
android:textSize="13sp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/home_compose_account_count"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/looply_space_sm"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:textColor="@color/home_text_secondary"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/home_compose_account_check"
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:layout_marginStart="@dimen/looply_space_sm"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_home_check"
|
||||||
|
android:tint="@color/home_accent"
|
||||||
|
android:visibility="gone" />
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/home_compose_account_list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical" />
|
||||||
@@ -3,7 +3,9 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="@color/home_sheet_scrim"
|
android:background="@color/home_sheet_scrim"
|
||||||
android:fitsSystemWindows="true">
|
android:fitsSystemWindows="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:focusableInTouchMode="true">
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:id="@+id/home_compose_scrim"
|
android:id="@+id/home_compose_scrim"
|
||||||
@@ -94,46 +96,84 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/home_compose_create_group"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/home_compose_group_button_height"
|
|
||||||
android:layout_marginStart="@dimen/looply_space_md"
|
|
||||||
android:layout_marginTop="@dimen/looply_space_sm"
|
|
||||||
android:layout_marginEnd="@dimen/looply_space_md"
|
|
||||||
android:background="@drawable/home_bg_sheet_action"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
android:gravity="center"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="22dp"
|
|
||||||
android:layout_height="22dp"
|
|
||||||
android:contentDescription="@null"
|
|
||||||
android:scaleType="fitCenter"
|
|
||||||
android:src="@drawable/ic_home_group_add"
|
|
||||||
android:tint="@color/home_text_primary" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="@dimen/looply_space_sm"
|
|
||||||
android:includeFontPadding="false"
|
|
||||||
android:text="@string/home_compose_create_group"
|
|
||||||
android:textColor="@color/home_text_primary"
|
|
||||||
android:textSize="15sp" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingStart="@dimen/looply_space_lg"
|
android:gravity="center_vertical"
|
||||||
android:paddingTop="@dimen/looply_space_lg"
|
android:orientation="horizontal"
|
||||||
android:paddingEnd="@dimen/looply_space_lg"
|
android:paddingStart="@dimen/looply_screen_padding"
|
||||||
android:paddingBottom="@dimen/looply_space_xs"
|
android:paddingTop="@dimen/looply_space_md"
|
||||||
android:text="@string/home_compose_contacts_header"
|
android:paddingEnd="@dimen/looply_screen_padding"
|
||||||
android:textColor="@color/home_text_secondary"
|
android:paddingBottom="@dimen/looply_space_xs">
|
||||||
android:textSize="13sp" />
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/home_compose_account_chip"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="36dp"
|
||||||
|
android:background="@drawable/home_bg_compose_account_chip"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="10dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="18dp"
|
||||||
|
android:layout_height="18dp"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_home_menu_person"
|
||||||
|
android:tint="@color/home_text_primary" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/home_compose_account_label"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:text="@string/home_compose_all_contacts"
|
||||||
|
android:textColor="@color/home_text_primary"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="16dp"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_home_expand" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/home_compose_create_group"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="36dp"
|
||||||
|
android:background="@drawable/home_bg_compose_group_pill"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingStart="14dp"
|
||||||
|
android:paddingEnd="14dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="18dp"
|
||||||
|
android:layout_height="18dp"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_home_group_add"
|
||||||
|
android:tint="@color/home_accent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="6dp"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:text="@string/home_compose_create_group"
|
||||||
|
android:textColor="@color/home_accent"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@@ -24,6 +24,8 @@
|
|||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:divider="@null"
|
android:divider="@null"
|
||||||
android:dividerHeight="0dp"
|
android:dividerHeight="0dp"
|
||||||
|
android:paddingStart="@dimen/looply_screen_padding"
|
||||||
|
android:paddingEnd="@dimen/looply_screen_padding"
|
||||||
android:paddingBottom="24dp"
|
android:paddingBottom="24dp"
|
||||||
android:scrollbarStyle="outsideOverlay" />
|
android:scrollbarStyle="outsideOverlay" />
|
||||||
|
|
||||||
|
|||||||
@@ -4,79 +4,118 @@
|
|||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:orientation="vertical">
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:minHeight="56dp"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:paddingStart="16dp"
|
|
||||||
android:paddingTop="8dp"
|
|
||||||
android:paddingEnd="16dp"
|
|
||||||
android:paddingBottom="8dp">
|
|
||||||
|
|
||||||
<TextView
|
<View
|
||||||
android:id="@+id/alphabet_header"
|
android:id="@+id/contact_section_gap_top"
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="@dimen/looply_space_sm"
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<com.android.messaging.ui.ContactIconView
|
|
||||||
android:id="@+id/contact_icon"
|
|
||||||
android:layout_width="@dimen/looply_avatar_size"
|
|
||||||
android:layout_height="@dimen/looply_avatar_size"
|
|
||||||
android:contentDescription="@null"
|
|
||||||
app:contactIconSize="normal" />
|
|
||||||
|
|
||||||
<!-- Covers the avatar while the row is picked for a group -->
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/contact_checkmark"
|
|
||||||
android:layout_width="@dimen/looply_avatar_size"
|
|
||||||
android:layout_height="@dimen/looply_avatar_size"
|
|
||||||
android:contentDescription="@null"
|
|
||||||
android:src="@drawable/home_bg_contact_selected"
|
|
||||||
android:visibility="gone" />
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/work_profile_icon"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:visibility="gone" />
|
android:visibility="gone" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="0dp"
|
android:id="@+id/contact_section_card"
|
||||||
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="12dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/contact_name"
|
android:id="@+id/alphabet_header"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:ellipsize="end"
|
android:includeFontPadding="false"
|
||||||
android:maxLines="1"
|
android:paddingStart="16dp"
|
||||||
android:textColor="@color/home_text_primary"
|
android:paddingTop="14dp"
|
||||||
android:textSize="16sp"
|
android:paddingEnd="16dp"
|
||||||
android:textStyle="bold" />
|
android:paddingBottom="2dp"
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/contact_details"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="2dp"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:textColor="@color/home_text_secondary"
|
android:textColor="@color/home_text_secondary"
|
||||||
android:textSize="14sp" />
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
<TextView
|
<LinearLayout
|
||||||
android:id="@+id/contact_detail_type"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="0dp"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="0dp"
|
android:gravity="center_vertical"
|
||||||
|
android:minHeight="56dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingTop="8dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:paddingBottom="8dp">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<com.android.messaging.ui.ContactIconView
|
||||||
|
android:id="@+id/contact_icon"
|
||||||
|
android:layout_width="@dimen/looply_avatar_size"
|
||||||
|
android:layout_height="@dimen/looply_avatar_size"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
app:contactIconSize="normal" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/contact_checkmark"
|
||||||
|
android:layout_width="@dimen/looply_avatar_size"
|
||||||
|
android:layout_height="@dimen/looply_avatar_size"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/home_bg_contact_selected"
|
||||||
|
android:visibility="gone" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/work_profile_icon"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/contact_name"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="@color/home_text_primary"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/contact_details"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="@color/home_text_secondary"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/contact_detail_type"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/contact_section_divider"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_marginStart="66dp"
|
||||||
|
android:background="@color/home_divider"
|
||||||
android:visibility="gone" />
|
android:visibility="gone" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/contact_section_gap_bottom"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/looply_space_xs"
|
||||||
|
android:visibility="gone" />
|
||||||
</com.android.messaging.ui.contact.ContactListItemView>
|
</com.android.messaging.ui.contact.ContactListItemView>
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="@color/home_bg"
|
android:background="@color/home_bg"
|
||||||
android:fitsSystemWindows="true"
|
android:fitsSystemWindows="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:focusableInTouchMode="true"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<include layout="@layout/home_sub_header" />
|
<include layout="@layout/home_sub_header" />
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/home_bg_compose_group"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:minHeight="@dimen/looply_button_height"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="@dimen/looply_space_lg"
|
||||||
|
android:paddingEnd="@dimen/looply_space_lg">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/home_language_title"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="@color/home_text_primary"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/home_language_check"
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:layout_marginStart="@dimen/looply_space_sm"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_home_check"
|
||||||
|
android:tint="@color/home_accent"
|
||||||
|
android:visibility="gone" />
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/home_language_list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical" />
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
|
android:elevation="24dp"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:visibility="gone">
|
android:visibility="gone">
|
||||||
|
|
||||||
@@ -21,12 +22,12 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:contentDescription="@null"
|
android:contentDescription="@null"
|
||||||
android:scaleType="centerCrop" />
|
android:scaleType="fitXY" />
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="#4D1B1528"
|
android:background="#801B1528"
|
||||||
android:importantForAccessibility="no" />
|
android:importantForAccessibility="no" />
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/home_bg"
|
||||||
|
android:fitsSystemWindows="true"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<include layout="@layout/home_sub_header" />
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
|
<WebView
|
||||||
|
android:id="@+id/home_location_map"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:translationY="-16dp"
|
||||||
|
app:srcCompat="@drawable/ic_home_place_line"
|
||||||
|
app:tint="@color/home_accent" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/home_location_my_location"
|
||||||
|
android:layout_width="44dp"
|
||||||
|
android:layout_height="44dp"
|
||||||
|
android:layout_gravity="bottom|end"
|
||||||
|
android:layout_marginEnd="@dimen/looply_screen_padding"
|
||||||
|
android:layout_marginBottom="20dp"
|
||||||
|
android:background="@drawable/home_bg_map_locate"
|
||||||
|
android:contentDescription="@string/home_location_my_location"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:scaleType="centerInside"
|
||||||
|
app:srcCompat="@drawable/ic_home_place_line"
|
||||||
|
app:tint="@color/home_text_primary" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/home_row_bg"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:minHeight="72dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="@dimen/looply_screen_padding"
|
||||||
|
android:paddingTop="@dimen/looply_space_md"
|
||||||
|
android:paddingEnd="@dimen/looply_screen_padding"
|
||||||
|
android:paddingBottom="@dimen/looply_space_md">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="22dp"
|
||||||
|
android:layout_height="22dp"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
app:srcCompat="@drawable/ic_home_place_line"
|
||||||
|
app:tint="@color/home_accent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/home_location_address"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/looply_space_md"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:maxLines="2"
|
||||||
|
android:text="@string/home_location_finding"
|
||||||
|
android:textColor="@color/home_text_primary"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/home_location_send"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginStart="@dimen/looply_space_sm"
|
||||||
|
android:background="@drawable/home_bg_compose_send_circle"
|
||||||
|
android:contentDescription="@string/home_location_send"
|
||||||
|
android:padding="6dp"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:src="@drawable/ic_home_send_up" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/home_lock_method_pin"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/home_bg_lock_method"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:minHeight="@dimen/looply_button_height"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="@dimen/looply_space_lg"
|
||||||
|
android:paddingEnd="@dimen/looply_space_lg">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_home_pin"
|
||||||
|
android:tint="@color/home_text_primary" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/looply_space_md"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:text="@string/home_lock_method_pin"
|
||||||
|
android:textColor="@color/home_text_primary"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:text="@string/home_lock_method_pin_subtitle"
|
||||||
|
android:textColor="@color/home_text_secondary"
|
||||||
|
android:textSize="13sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/home_lock_method_pattern"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/looply_space_sm"
|
||||||
|
android:background="@drawable/home_bg_lock_method"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:minHeight="@dimen/looply_button_height"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="@dimen/looply_space_lg"
|
||||||
|
android:paddingEnd="@dimen/looply_space_lg">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_home_pattern"
|
||||||
|
android:tint="@color/home_text_primary" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/looply_space_md"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:text="@string/home_lock_method_pattern"
|
||||||
|
android:textColor="@color/home_text_primary"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:text="@string/home_lock_method_pattern_subtitle"
|
||||||
|
android:textColor="@color/home_text_secondary"
|
||||||
|
android:textSize="13sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/home_lock_method_password"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/looply_space_sm"
|
||||||
|
android:background="@drawable/home_bg_lock_method"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:minHeight="@dimen/looply_button_height"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="@dimen/looply_space_lg"
|
||||||
|
android:paddingEnd="@dimen/looply_space_lg">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_home_password"
|
||||||
|
android:tint="@color/home_text_primary" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/looply_space_md"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:text="@string/home_lock_method_password"
|
||||||
|
android:textColor="@color/home_text_primary"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:text="@string/home_lock_method_password_subtitle"
|
||||||
|
android:textColor="@color/home_text_secondary"
|
||||||
|
android:textSize="13sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/home_lock_method_continue"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="52dp"
|
||||||
|
android:layout_marginTop="@dimen/looply_space_lg"
|
||||||
|
android:background="@drawable/home_bg_lock_continue"
|
||||||
|
android:gravity="center"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:text="@string/home_lock_continue"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/home_lock_password_label"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:text="@string/home_lock_password_hint"
|
||||||
|
android:textColor="@color/home_text_secondary"
|
||||||
|
android:textSize="13sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:addStatesFromChildren="true"
|
||||||
|
android:background="@drawable/home_bg_modal_input"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:minHeight="@dimen/looply_input_height"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="@dimen/looply_space_lg"
|
||||||
|
android:paddingEnd="@dimen/looply_space_sm">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/home_lock_password"
|
||||||
|
style="@style/Home.EditText"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:imeOptions="actionDone"
|
||||||
|
android:importantForAutofill="no"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:inputType="textPassword"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textColor="@color/home_text_primary"
|
||||||
|
android:textColorHint="@color/home_text_secondary"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/home_lock_password_toggle"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@string/home_lock_password_toggle"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
app:srcCompat="@drawable/ic_home_visibility"
|
||||||
|
app:tint="@color/home_text_secondary" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/home_lock_password_rules"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/home_lock_rule_length"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:drawablePadding="10dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:paddingTop="4dp"
|
||||||
|
android:paddingBottom="4dp"
|
||||||
|
android:text="@string/home_lock_rule_length"
|
||||||
|
android:textColor="@color/home_text_secondary"
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:drawableStartCompat="@drawable/ic_home_check"
|
||||||
|
app:drawableTint="@color/home_text_tertiary" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/home_lock_rule_number"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:drawablePadding="10dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:paddingTop="4dp"
|
||||||
|
android:paddingBottom="4dp"
|
||||||
|
android:text="@string/home_lock_rule_number"
|
||||||
|
android:textColor="@color/home_text_secondary"
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:drawableStartCompat="@drawable/ic_home_check"
|
||||||
|
app:drawableTint="@color/home_text_tertiary" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/home_lock_rule_symbol"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:drawablePadding="10dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:paddingTop="4dp"
|
||||||
|
android:paddingBottom="4dp"
|
||||||
|
android:text="@string/home_lock_rule_symbol"
|
||||||
|
android:textColor="@color/home_text_secondary"
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:drawableStartCompat="@drawable/ic_home_check"
|
||||||
|
app:drawableTint="@color/home_text_tertiary" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/home_lock_rule_upper"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:drawablePadding="10dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:paddingTop="4dp"
|
||||||
|
android:paddingBottom="4dp"
|
||||||
|
android:text="@string/home_lock_rule_upper"
|
||||||
|
android:textColor="@color/home_text_secondary"
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:drawableStartCompat="@drawable/ic_home_check"
|
||||||
|
app:drawableTint="@color/home_text_tertiary" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
|
<com.android.messaging.ui.home.HomePatternLockView
|
||||||
|
android:id="@+id/home_lock_pattern"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="center" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/home_lock_pattern_recorded"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/looply_space_md"
|
||||||
|
android:background="@drawable/home_bg_schedule_chip"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:paddingStart="14dp"
|
||||||
|
android:paddingTop="8dp"
|
||||||
|
android:paddingEnd="14dp"
|
||||||
|
android:paddingBottom="8dp"
|
||||||
|
android:text="@string/home_lock_pattern_recorded"
|
||||||
|
android:textColor="@color/home_text_primary"
|
||||||
|
android:textSize="13sp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/home_lock_pattern_clear"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/looply_space_sm"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:padding="@dimen/looply_space_sm"
|
||||||
|
android:text="@string/home_lock_clear_pattern"
|
||||||
|
android:textColor="@color/home_accent"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/home_lock_pin_dots"
|
||||||
|
layout="@layout/home_lock_pin_dots"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/home_lock_pin_keypad"
|
||||||
|
layout="@layout/home_lock_pin_keypad"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginTop="@dimen/looply_space_xl"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/home_lock_pin_dots"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:background="@drawable/home_bg_pin_dot" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:background="@drawable/home_bg_pin_dot" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:background="@drawable/home_bg_pin_dot" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:background="@drawable/home_bg_pin_dot" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:background="@drawable/home_bg_pin_dot" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:background="@drawable/home_bg_pin_dot" />
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/home_lock_pin_keypad"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingTop="@dimen/looply_space_md"
|
||||||
|
android:paddingBottom="@dimen/looply_space_md">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView style="@style/Home.LockKey" android:text="1" />
|
||||||
|
<TextView style="@style/Home.LockKey" android:text="2" />
|
||||||
|
<TextView style="@style/Home.LockKey" android:text="3" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView style="@style/Home.LockKey" android:text="4" />
|
||||||
|
<TextView style="@style/Home.LockKey" android:text="5" />
|
||||||
|
<TextView style="@style/Home.LockKey" android:text="6" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView style="@style/Home.LockKey" android:text="7" />
|
||||||
|
<TextView style="@style/Home.LockKey" android:text="8" />
|
||||||
|
<TextView style="@style/Home.LockKey" android:text="9" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="96dp"
|
||||||
|
android:layout_height="96dp" />
|
||||||
|
|
||||||
|
<TextView style="@style/Home.LockKey" android:text="0" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/home_lock_pin_backspace"
|
||||||
|
android:layout_width="96dp"
|
||||||
|
android:layout_height="96dp"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@string/home_lock_backspace"
|
||||||
|
android:padding="28dp"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
app:srcCompat="@drawable/ic_home_backspace"
|
||||||
|
app:tint="@color/home_text_primary" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/home_bg"
|
||||||
|
android:fitsSystemWindows="true"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<include layout="@layout/home_sub_header" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingStart="24dp"
|
||||||
|
android:paddingEnd="24dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/home_lock_subtitle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/looply_space_md"
|
||||||
|
android:gravity="start"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:lineSpacingExtra="3dp"
|
||||||
|
android:textColor="@color/home_text_secondary"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/home_lock_error"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:textColor="@color/home_destructive"
|
||||||
|
android:textSize="13sp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/home_lock_body"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginTop="28dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/home_bg"
|
||||||
|
android:fitsSystemWindows="true"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/home_lock_app_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/home_header_height"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:paddingStart="@dimen/looply_screen_padding"
|
||||||
|
android:paddingEnd="@dimen/looply_screen_padding"
|
||||||
|
android:text="@string/home_title"
|
||||||
|
android:textColor="@color/home_text_primary"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingStart="24dp"
|
||||||
|
android:paddingEnd="24dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
app:srcCompat="@drawable/ic_home_lock"
|
||||||
|
app:tint="@color/home_accent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:text="@string/home_lock_enter_title"
|
||||||
|
android:textColor="@color/home_text_primary"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/home_lock_subtitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:textColor="@color/home_text_secondary"
|
||||||
|
android:textSize="13sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/home_lock_error"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:textColor="@color/home_destructive"
|
||||||
|
android:textSize="13sp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/home_lock_body"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/home_message_status_resend"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/looply_button_height"
|
||||||
|
android:background="@drawable/home_bg_sheet_action"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="@dimen/looply_space_lg"
|
||||||
|
android:paddingEnd="@dimen/looply_space_lg">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="22dp"
|
||||||
|
android:layout_height="22dp"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
app:srcCompat="@drawable/ic_home_resend"
|
||||||
|
app:tint="@color/home_text_primary" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/looply_space_md"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:text="@string/home_message_action_send"
|
||||||
|
android:textColor="@color/home_text_primary"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/home_message_status_delete"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/looply_button_height"
|
||||||
|
android:layout_marginTop="@dimen/looply_space_sm"
|
||||||
|
android:background="@drawable/home_bg_sheet_action"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="@dimen/looply_space_lg"
|
||||||
|
android:paddingEnd="@dimen/looply_space_lg">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="22dp"
|
||||||
|
android:layout_height="22dp"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
app:srcCompat="@drawable/ic_home_menu_trash"
|
||||||
|
app:tint="@color/home_destructive" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/looply_space_md"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:text="@string/home_message_action_delete"
|
||||||
|
android:textColor="@color/home_destructive"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="@dimen/home_conversation_compose_pill_height"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:addStatesFromChildren="true"
|
||||||
|
android:background="@drawable/home_bg_compose_pill"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="@dimen/looply_space_xs"
|
||||||
|
android:paddingEnd="@dimen/looply_space_xs">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/home_notification_reply_input"
|
||||||
|
style="@style/Home.EditText"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:hint="@string/home_notification_reply_hint"
|
||||||
|
android:imeOptions="actionSend|flagNoEnterAction|flagNoExtractUi"
|
||||||
|
android:importantForAutofill="no"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:inputType="textCapSentences|textMultiLine"
|
||||||
|
android:maxLines="3"
|
||||||
|
android:paddingStart="@dimen/looply_space_md"
|
||||||
|
android:paddingEnd="@dimen/looply_space_md"
|
||||||
|
android:textColor="@color/home_text_primary"
|
||||||
|
android:textColorHint="@color/home_text_secondary"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/home_notification_reply_send"
|
||||||
|
android:layout_width="@dimen/home_conversation_compose_send_size"
|
||||||
|
android:layout_height="@dimen/home_conversation_compose_send_size"
|
||||||
|
android:layout_marginStart="@dimen/looply_space_sm"
|
||||||
|
android:background="@drawable/home_bg_compose_send_circle"
|
||||||
|
android:contentDescription="@string/sendButtonContentDescription"
|
||||||
|
android:padding="4dp"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:src="@drawable/ic_home_send_up"
|
||||||
|
app:tint="@color/home_accent_soft" />
|
||||||
|
</LinearLayout>
|
||||||
@@ -221,6 +221,33 @@
|
|||||||
android:layout_marginBottom="4dp"
|
android:layout_marginBottom="4dp"
|
||||||
android:background="@color/home_divider" />
|
android:background="@color/home_divider" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/popover_share_app"
|
||||||
|
style="@style/Home.PopoverItem"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="44dp">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="18dp"
|
||||||
|
android:layout_height="18dp"
|
||||||
|
android:layout_marginEnd="8dp" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="22dp"
|
||||||
|
android:layout_height="22dp"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_share_light"
|
||||||
|
android:tint="@color/home_text_primary" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/Home.PopoverLabel"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/home_popover_share_app" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/popover_settings"
|
android:id="@+id/popover_settings"
|
||||||
style="@style/Home.PopoverItem"
|
style="@style/Home.PopoverItem"
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingStart="@dimen/looply_space_sm"
|
||||||
|
android:paddingEnd="@dimen/looply_space_sm">
|
||||||
|
|
||||||
|
<DatePicker
|
||||||
|
android:id="@+id/home_schedule_date"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:calendarViewShown="true"
|
||||||
|
android:datePickerMode="calendar"
|
||||||
|
android:spinnersShown="false"
|
||||||
|
android:theme="@style/Home.Picker" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="end"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingTop="@dimen/looply_space_sm"
|
||||||
|
android:paddingBottom="@dimen/looply_space_sm">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/home_schedule_cancel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:padding="@dimen/looply_space_md"
|
||||||
|
android:text="@string/home_schedule_cancel"
|
||||||
|
android:textColor="@color/home_accent"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/home_schedule_next"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:padding="@dimen/looply_space_md"
|
||||||
|
android:text="@string/home_schedule_next"
|
||||||
|
android:textColor="@color/home_accent"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingStart="@dimen/looply_space_sm"
|
||||||
|
android:paddingEnd="@dimen/looply_space_sm">
|
||||||
|
|
||||||
|
<TimePicker
|
||||||
|
android:id="@+id/home_schedule_time"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:theme="@style/Home.Picker"
|
||||||
|
android:timePickerMode="clock" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="end"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingTop="@dimen/looply_space_sm"
|
||||||
|
android:paddingBottom="@dimen/looply_space_sm">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/home_schedule_cancel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:padding="@dimen/looply_space_md"
|
||||||
|
android:text="@string/home_schedule_back"
|
||||||
|
android:textColor="@color/home_accent"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/home_schedule_next"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:padding="@dimen/looply_space_md"
|
||||||
|
android:text="@string/home_schedule_done"
|
||||||
|
android:textColor="@color/home_accent"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
@@ -5,6 +5,8 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="@color/home_bg"
|
android:background="@color/home_bg"
|
||||||
android:fitsSystemWindows="true"
|
android:fitsSystemWindows="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:focusableInTouchMode="true"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<include layout="@layout/home_sub_header" />
|
<include layout="@layout/home_sub_header" />
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/home_bg"
|
||||||
|
android:fitsSystemWindows="true"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<include layout="@layout/home_sub_header" />
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:paddingStart="@dimen/looply_space_lg"
|
||||||
|
android:paddingTop="@dimen/looply_space_md"
|
||||||
|
android:paddingEnd="@dimen/looply_space_lg"
|
||||||
|
android:paddingBottom="@dimen/looply_screen_padding"
|
||||||
|
android:scrollbars="none">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/home_bg_profile_card"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="@dimen/looply_space_sm">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:paddingStart="@dimen/looply_space_md"
|
||||||
|
android:paddingTop="@dimen/looply_space_xs"
|
||||||
|
android:paddingEnd="@dimen/looply_space_md"
|
||||||
|
android:paddingBottom="@dimen/looply_space_sm"
|
||||||
|
android:text="@string/home_settings_app_lock"
|
||||||
|
android:textColor="@color/home_accent"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/home_security_app_lock"
|
||||||
|
layout="@layout/home_settings_row"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/home_security_set_pin"
|
||||||
|
layout="@layout/home_settings_row"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/looply_space_xs" />
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/home_security_set_pattern"
|
||||||
|
layout="@layout/home_settings_row"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/looply_space_xs" />
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/home_security_set_password"
|
||||||
|
layout="@layout/home_settings_row"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/looply_space_xs" />
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
||||||
|
</LinearLayout>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user