Loading services/people/java/com/android/server/people/data/AbstractProtoDiskReadWriter.java 0 → 100644 +264 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.people.data; import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.WorkerThread; import android.text.format.DateUtils; import android.util.ArrayMap; import android.util.AtomicFile; import android.util.Slog; import android.util.proto.ProtoInputStream; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.Arrays; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; /** * Base class for reading and writing protobufs on disk from a root directory. Callers should * ensure that the root directory is unlocked before doing I/O operations using this class. * * @param <T> is the data class representation of a protobuf. */ abstract class AbstractProtoDiskReadWriter<T> { private static final String TAG = AbstractProtoDiskReadWriter.class.getSimpleName(); private static final long SHUTDOWN_DISK_WRITE_TIMEOUT = 5L * DateUtils.SECOND_IN_MILLIS; private final File mRootDir; private final ScheduledExecutorService mScheduledExecutorService; private final long mWriteDelayMs; @GuardedBy("this") private ScheduledFuture<?> mScheduledFuture; @GuardedBy("this") private Map<String, T> mScheduledFileDataMap = new ArrayMap<>(); /** * Child class shall provide a {@link ProtoStreamWriter} to facilitate the writing of data as a * protobuf on disk. */ abstract ProtoStreamWriter<T> protoStreamWriter(); /** * Child class shall provide a {@link ProtoStreamReader} to facilitate the reading of protobuf * data on disk. */ abstract ProtoStreamReader<T> protoStreamReader(); AbstractProtoDiskReadWriter(@NonNull File rootDir, long writeDelayMs, @NonNull ScheduledExecutorService scheduledExecutorService) { mRootDir = rootDir; mWriteDelayMs = writeDelayMs; mScheduledExecutorService = scheduledExecutorService; } @WorkerThread void delete(@NonNull String fileName) { final File file = getFile(fileName); if (!file.exists()) { return; } if (!file.delete()) { Slog.e(TAG, "Failed to delete file: " + file.getPath()); } } @WorkerThread void writeTo(@NonNull String fileName, @NonNull T data) { final File file = getFile(fileName); final AtomicFile atomicFile = new AtomicFile(file); FileOutputStream fileOutputStream = null; try { fileOutputStream = atomicFile.startWrite(); } catch (IOException e) { Slog.e(TAG, "Failed to write to protobuf file.", e); return; } try { final ProtoOutputStream protoOutputStream = new ProtoOutputStream(fileOutputStream); protoStreamWriter().write(protoOutputStream, data); protoOutputStream.flush(); atomicFile.finishWrite(fileOutputStream); fileOutputStream = null; } finally { // When fileInputStream is null (successful write), this will no-op. atomicFile.failWrite(fileOutputStream); } } @WorkerThread @Nullable T read(@NonNull String fileName) { File[] files = mRootDir.listFiles( pathname -> pathname.isFile() && pathname.getName().equals(fileName)); if (files == null || files.length == 0) { return null; } else if (files.length > 1) { // This can't possibly happen, but sanity check. Slog.w(TAG, "Found multiple files with the same name: " + Arrays.toString(files)); } return parseFile(files[0]); } /** * Reads all files in directory and returns a map with file names as keys and parsed file * contents as values. */ @WorkerThread @Nullable Map<String, T> readAll() { File[] files = mRootDir.listFiles(File::isFile); if (files == null) { return null; } Map<String, T> results = new ArrayMap<>(); for (File file : files) { T result = parseFile(file); if (result != null) { results.put(file.getName(), result); } } return results; } /** * Schedules the specified data to be flushed to a file in the future. Subsequent * calls for the same file before the flush occurs will replace the previous data but will not * reset when the flush will occur. All unique files will be flushed at the same time. */ @MainThread synchronized void scheduleSave(@NonNull String fileName, @NonNull T data) { mScheduledFileDataMap.put(fileName, data); if (mScheduledExecutorService.isShutdown()) { Slog.e(TAG, "Worker is shutdown, failed to schedule data saving."); return; } // Skip scheduling another flush when one is pending. if (mScheduledFuture != null) { return; } mScheduledFuture = mScheduledExecutorService.schedule(this::flushScheduledData, mWriteDelayMs, TimeUnit.MILLISECONDS); } /** * Saves specified data immediately on a background thread, and blocks until its completed. This * is useful for when device is powering off. */ @MainThread synchronized void saveImmediately(@NonNull String fileName, @NonNull T data) { if (mScheduledExecutorService.isShutdown()) { return; } // Cancel existing future. if (mScheduledFuture != null) { // We shouldn't need to interrupt as this method and threaded task // #flushScheduledData are both synchronized. mScheduledFuture.cancel(true); } mScheduledFileDataMap.put(fileName, data); // Submit flush and blocks until it completes. Blocking will prevent the device from // shutting down before flushing completes. Future<?> future = mScheduledExecutorService.submit(this::flushScheduledData); try { future.get(SHUTDOWN_DISK_WRITE_TIMEOUT, TimeUnit.MILLISECONDS); } catch (InterruptedException | ExecutionException | TimeoutException e) { Slog.e(TAG, "Failed to save data immediately.", e); } } @WorkerThread private synchronized void flushScheduledData() { if (mScheduledFileDataMap.isEmpty()) { mScheduledFuture = null; return; } for (String fileName : mScheduledFileDataMap.keySet()) { T data = mScheduledFileDataMap.remove(fileName); writeTo(fileName, data); } mScheduledFuture = null; } @WorkerThread @Nullable private T parseFile(@NonNull File file) { final AtomicFile atomicFile = new AtomicFile(file); try (FileInputStream fileInputStream = atomicFile.openRead()) { final ProtoInputStream protoInputStream = new ProtoInputStream(fileInputStream); return protoStreamReader().read(protoInputStream); } catch (IOException e) { Slog.e(TAG, "Failed to parse protobuf file.", e); } return null; } @NonNull private File getFile(String fileName) { return new File(mRootDir, fileName); } /** * {@code ProtoStreamWriter} writes {@code T} fields to {@link ProtoOutputStream}. * * @param <T> is the data class representation of a protobuf. */ interface ProtoStreamWriter<T> { /** * Writes {@code T} to {@link ProtoOutputStream}. */ void write(@NonNull ProtoOutputStream protoOutputStream, @NonNull T data); } /** * {@code ProtoStreamReader} reads {@link ProtoInputStream} and translate it to {@code T}. * * @param <T> is the data class representation of a protobuf. */ interface ProtoStreamReader<T> { /** * Reads {@link ProtoInputStream} and translates it to {@code T}. */ @Nullable T read(@NonNull ProtoInputStream protoInputStream); } } services/people/java/com/android/server/people/data/ConversationInfo.java +74 −0 Original line number Diff line number Diff line Loading @@ -20,12 +20,18 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.LocusId; import android.content.LocusIdProto; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutInfo.ShortcutFlags; import android.net.Uri; import android.util.Slog; import android.util.proto.ProtoInputStream; import android.util.proto.ProtoOutputStream; import com.android.internal.util.Preconditions; import com.android.server.people.ConversationInfoProto; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Objects; Loading @@ -35,6 +41,8 @@ import java.util.Objects; */ public class ConversationInfo { private static final String TAG = ConversationInfo.class.getSimpleName(); private static final int FLAG_IMPORTANT = 1; private static final int FLAG_NOTIFICATION_SILENCED = 1 << 1; Loading Loading @@ -241,6 +249,72 @@ public class ConversationInfo { return (mConversationFlags & flags) == flags; } /** Writes field members to {@link ProtoOutputStream}. */ void writeToProto(@NonNull ProtoOutputStream protoOutputStream) { protoOutputStream.write(ConversationInfoProto.SHORTCUT_ID, mShortcutId); if (mLocusId != null) { long locusIdToken = protoOutputStream.start(ConversationInfoProto.LOCUS_ID_PROTO); protoOutputStream.write(LocusIdProto.LOCUS_ID, mLocusId.getId()); protoOutputStream.end(locusIdToken); } if (mContactUri != null) { protoOutputStream.write(ConversationInfoProto.CONTACT_URI, mContactUri.toString()); } if (mNotificationChannelId != null) { protoOutputStream.write(ConversationInfoProto.NOTIFICATION_CHANNEL_ID, mNotificationChannelId); } protoOutputStream.write(ConversationInfoProto.SHORTCUT_FLAGS, mShortcutFlags); protoOutputStream.write(ConversationInfoProto.CONVERSATION_FLAGS, mConversationFlags); } /** Reads from {@link ProtoInputStream} and constructs a {@link ConversationInfo}. */ @NonNull static ConversationInfo readFromProto(@NonNull ProtoInputStream protoInputStream) throws IOException { ConversationInfo.Builder builder = new ConversationInfo.Builder(); while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { switch (protoInputStream.getFieldNumber()) { case (int) ConversationInfoProto.SHORTCUT_ID: builder.setShortcutId( protoInputStream.readString(ConversationInfoProto.SHORTCUT_ID)); break; case (int) ConversationInfoProto.LOCUS_ID_PROTO: long locusIdToken = protoInputStream.start( ConversationInfoProto.LOCUS_ID_PROTO); while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { if (protoInputStream.getFieldNumber() == (int) LocusIdProto.LOCUS_ID) { builder.setLocusId(new LocusId( protoInputStream.readString(LocusIdProto.LOCUS_ID))); } } protoInputStream.end(locusIdToken); break; case (int) ConversationInfoProto.CONTACT_URI: builder.setContactUri(Uri.parse(protoInputStream.readString( ConversationInfoProto.CONTACT_URI))); break; case (int) ConversationInfoProto.NOTIFICATION_CHANNEL_ID: builder.setNotificationChannelId(protoInputStream.readString( ConversationInfoProto.NOTIFICATION_CHANNEL_ID)); break; case (int) ConversationInfoProto.SHORTCUT_FLAGS: builder.setShortcutFlags(protoInputStream.readInt( ConversationInfoProto.SHORTCUT_FLAGS)); break; case (int) ConversationInfoProto.CONVERSATION_FLAGS: builder.setConversationFlags(protoInputStream.readInt( ConversationInfoProto.CONVERSATION_FLAGS)); break; default: Slog.w(TAG, "Could not read undefined field: " + protoInputStream.getFieldNumber()); } } return builder.build(); } /** * Builder class for {@link ConversationInfo} objects. */ Loading services/people/java/com/android/server/people/data/ConversationStore.java +227 −24 File changed.Preview size limit exceeded, changes collapsed. Show changes services/people/java/com/android/server/people/data/DataManager.java +16 −1 Original line number Diff line number Diff line Loading @@ -86,6 +86,7 @@ public class DataManager { private final Context mContext; private final Injector mInjector; private final ScheduledExecutorService mUsageStatsQueryExecutor; private final ScheduledExecutorService mDiskReadWriterExecutor; private final SparseArray<UserData> mUserDataArray = new SparseArray<>(); private final SparseArray<BroadcastReceiver> mBroadcastReceivers = new SparseArray<>(); Loading Loading @@ -113,6 +114,7 @@ public class DataManager { BackgroundThread.getHandler()); mMmsSmsContentObserver = new MmsSmsContentObserver( BackgroundThread.getHandler()); mDiskReadWriterExecutor = mInjector.createScheduledExecutor(); } /** Initialization. Called when the system services are up running. */ Loading @@ -122,13 +124,18 @@ public class DataManager { mUserManager = mContext.getSystemService(UserManager.class); mShortcutServiceInternal.addListener(new ShortcutServiceListener()); IntentFilter shutdownIntentFilter = new IntentFilter(Intent.ACTION_SHUTDOWN); BroadcastReceiver shutdownBroadcastReceiver = new ShutdownBroadcastReceiver(); mContext.registerReceiver(shutdownBroadcastReceiver, shutdownIntentFilter); } /** This method is called when a user is unlocked. */ public void onUserUnlocked(int userId) { UserData userData = mUserDataArray.get(userId); if (userData == null) { userData = new UserData(userId); userData = new UserData(userId, mDiskReadWriterExecutor, mInjector.createContactsQueryHelper(mContext)); mUserDataArray.put(userId, userData); } userData.setUserUnlocked(); Loading Loading @@ -662,6 +669,14 @@ public class DataManager { } } private class ShutdownBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { forAllPackages(PackageData::saveToDisk); } } @VisibleForTesting static class Injector { Loading services/people/java/com/android/server/people/data/PackageData.java +23 −2 Original line number Diff line number Diff line Loading @@ -22,6 +22,8 @@ import android.annotation.UserIdInt; import android.content.LocusId; import android.text.TextUtils; import java.io.File; import java.util.concurrent.ScheduledExecutorService; import java.util.function.Consumer; import java.util.function.Predicate; Loading @@ -43,17 +45,36 @@ public class PackageData { private final Predicate<String> mIsDefaultSmsAppPredicate; private final File mPackageDataDir; PackageData(@NonNull String packageName, @UserIdInt int userId, @NonNull Predicate<String> isDefaultDialerPredicate, @NonNull Predicate<String> isDefaultSmsAppPredicate) { @NonNull Predicate<String> isDefaultSmsAppPredicate, @NonNull ScheduledExecutorService scheduledExecutorService, @NonNull File perUserPeopleDataDir, @NonNull ContactsQueryHelper helper) { mPackageName = packageName; mUserId = userId; mConversationStore = new ConversationStore(); mPackageDataDir = new File(perUserPeopleDataDir, mPackageName); mConversationStore = new ConversationStore(mPackageDataDir, scheduledExecutorService, helper); mEventStore = new EventStore(); mIsDefaultDialerPredicate = isDefaultDialerPredicate; mIsDefaultSmsAppPredicate = isDefaultSmsAppPredicate; } /** Called when user is unlocked. */ void loadFromDisk() { mPackageDataDir.mkdirs(); mConversationStore.loadConversationsFromDisk(); } /** Called when device is shutting down. */ void saveToDisk() { mConversationStore.saveConversationsToDisk(); } @NonNull public String getPackageName() { return mPackageName; Loading Loading
services/people/java/com/android/server/people/data/AbstractProtoDiskReadWriter.java 0 → 100644 +264 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.people.data; import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.WorkerThread; import android.text.format.DateUtils; import android.util.ArrayMap; import android.util.AtomicFile; import android.util.Slog; import android.util.proto.ProtoInputStream; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.Arrays; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; /** * Base class for reading and writing protobufs on disk from a root directory. Callers should * ensure that the root directory is unlocked before doing I/O operations using this class. * * @param <T> is the data class representation of a protobuf. */ abstract class AbstractProtoDiskReadWriter<T> { private static final String TAG = AbstractProtoDiskReadWriter.class.getSimpleName(); private static final long SHUTDOWN_DISK_WRITE_TIMEOUT = 5L * DateUtils.SECOND_IN_MILLIS; private final File mRootDir; private final ScheduledExecutorService mScheduledExecutorService; private final long mWriteDelayMs; @GuardedBy("this") private ScheduledFuture<?> mScheduledFuture; @GuardedBy("this") private Map<String, T> mScheduledFileDataMap = new ArrayMap<>(); /** * Child class shall provide a {@link ProtoStreamWriter} to facilitate the writing of data as a * protobuf on disk. */ abstract ProtoStreamWriter<T> protoStreamWriter(); /** * Child class shall provide a {@link ProtoStreamReader} to facilitate the reading of protobuf * data on disk. */ abstract ProtoStreamReader<T> protoStreamReader(); AbstractProtoDiskReadWriter(@NonNull File rootDir, long writeDelayMs, @NonNull ScheduledExecutorService scheduledExecutorService) { mRootDir = rootDir; mWriteDelayMs = writeDelayMs; mScheduledExecutorService = scheduledExecutorService; } @WorkerThread void delete(@NonNull String fileName) { final File file = getFile(fileName); if (!file.exists()) { return; } if (!file.delete()) { Slog.e(TAG, "Failed to delete file: " + file.getPath()); } } @WorkerThread void writeTo(@NonNull String fileName, @NonNull T data) { final File file = getFile(fileName); final AtomicFile atomicFile = new AtomicFile(file); FileOutputStream fileOutputStream = null; try { fileOutputStream = atomicFile.startWrite(); } catch (IOException e) { Slog.e(TAG, "Failed to write to protobuf file.", e); return; } try { final ProtoOutputStream protoOutputStream = new ProtoOutputStream(fileOutputStream); protoStreamWriter().write(protoOutputStream, data); protoOutputStream.flush(); atomicFile.finishWrite(fileOutputStream); fileOutputStream = null; } finally { // When fileInputStream is null (successful write), this will no-op. atomicFile.failWrite(fileOutputStream); } } @WorkerThread @Nullable T read(@NonNull String fileName) { File[] files = mRootDir.listFiles( pathname -> pathname.isFile() && pathname.getName().equals(fileName)); if (files == null || files.length == 0) { return null; } else if (files.length > 1) { // This can't possibly happen, but sanity check. Slog.w(TAG, "Found multiple files with the same name: " + Arrays.toString(files)); } return parseFile(files[0]); } /** * Reads all files in directory and returns a map with file names as keys and parsed file * contents as values. */ @WorkerThread @Nullable Map<String, T> readAll() { File[] files = mRootDir.listFiles(File::isFile); if (files == null) { return null; } Map<String, T> results = new ArrayMap<>(); for (File file : files) { T result = parseFile(file); if (result != null) { results.put(file.getName(), result); } } return results; } /** * Schedules the specified data to be flushed to a file in the future. Subsequent * calls for the same file before the flush occurs will replace the previous data but will not * reset when the flush will occur. All unique files will be flushed at the same time. */ @MainThread synchronized void scheduleSave(@NonNull String fileName, @NonNull T data) { mScheduledFileDataMap.put(fileName, data); if (mScheduledExecutorService.isShutdown()) { Slog.e(TAG, "Worker is shutdown, failed to schedule data saving."); return; } // Skip scheduling another flush when one is pending. if (mScheduledFuture != null) { return; } mScheduledFuture = mScheduledExecutorService.schedule(this::flushScheduledData, mWriteDelayMs, TimeUnit.MILLISECONDS); } /** * Saves specified data immediately on a background thread, and blocks until its completed. This * is useful for when device is powering off. */ @MainThread synchronized void saveImmediately(@NonNull String fileName, @NonNull T data) { if (mScheduledExecutorService.isShutdown()) { return; } // Cancel existing future. if (mScheduledFuture != null) { // We shouldn't need to interrupt as this method and threaded task // #flushScheduledData are both synchronized. mScheduledFuture.cancel(true); } mScheduledFileDataMap.put(fileName, data); // Submit flush and blocks until it completes. Blocking will prevent the device from // shutting down before flushing completes. Future<?> future = mScheduledExecutorService.submit(this::flushScheduledData); try { future.get(SHUTDOWN_DISK_WRITE_TIMEOUT, TimeUnit.MILLISECONDS); } catch (InterruptedException | ExecutionException | TimeoutException e) { Slog.e(TAG, "Failed to save data immediately.", e); } } @WorkerThread private synchronized void flushScheduledData() { if (mScheduledFileDataMap.isEmpty()) { mScheduledFuture = null; return; } for (String fileName : mScheduledFileDataMap.keySet()) { T data = mScheduledFileDataMap.remove(fileName); writeTo(fileName, data); } mScheduledFuture = null; } @WorkerThread @Nullable private T parseFile(@NonNull File file) { final AtomicFile atomicFile = new AtomicFile(file); try (FileInputStream fileInputStream = atomicFile.openRead()) { final ProtoInputStream protoInputStream = new ProtoInputStream(fileInputStream); return protoStreamReader().read(protoInputStream); } catch (IOException e) { Slog.e(TAG, "Failed to parse protobuf file.", e); } return null; } @NonNull private File getFile(String fileName) { return new File(mRootDir, fileName); } /** * {@code ProtoStreamWriter} writes {@code T} fields to {@link ProtoOutputStream}. * * @param <T> is the data class representation of a protobuf. */ interface ProtoStreamWriter<T> { /** * Writes {@code T} to {@link ProtoOutputStream}. */ void write(@NonNull ProtoOutputStream protoOutputStream, @NonNull T data); } /** * {@code ProtoStreamReader} reads {@link ProtoInputStream} and translate it to {@code T}. * * @param <T> is the data class representation of a protobuf. */ interface ProtoStreamReader<T> { /** * Reads {@link ProtoInputStream} and translates it to {@code T}. */ @Nullable T read(@NonNull ProtoInputStream protoInputStream); } }
services/people/java/com/android/server/people/data/ConversationInfo.java +74 −0 Original line number Diff line number Diff line Loading @@ -20,12 +20,18 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.LocusId; import android.content.LocusIdProto; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutInfo.ShortcutFlags; import android.net.Uri; import android.util.Slog; import android.util.proto.ProtoInputStream; import android.util.proto.ProtoOutputStream; import com.android.internal.util.Preconditions; import com.android.server.people.ConversationInfoProto; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Objects; Loading @@ -35,6 +41,8 @@ import java.util.Objects; */ public class ConversationInfo { private static final String TAG = ConversationInfo.class.getSimpleName(); private static final int FLAG_IMPORTANT = 1; private static final int FLAG_NOTIFICATION_SILENCED = 1 << 1; Loading Loading @@ -241,6 +249,72 @@ public class ConversationInfo { return (mConversationFlags & flags) == flags; } /** Writes field members to {@link ProtoOutputStream}. */ void writeToProto(@NonNull ProtoOutputStream protoOutputStream) { protoOutputStream.write(ConversationInfoProto.SHORTCUT_ID, mShortcutId); if (mLocusId != null) { long locusIdToken = protoOutputStream.start(ConversationInfoProto.LOCUS_ID_PROTO); protoOutputStream.write(LocusIdProto.LOCUS_ID, mLocusId.getId()); protoOutputStream.end(locusIdToken); } if (mContactUri != null) { protoOutputStream.write(ConversationInfoProto.CONTACT_URI, mContactUri.toString()); } if (mNotificationChannelId != null) { protoOutputStream.write(ConversationInfoProto.NOTIFICATION_CHANNEL_ID, mNotificationChannelId); } protoOutputStream.write(ConversationInfoProto.SHORTCUT_FLAGS, mShortcutFlags); protoOutputStream.write(ConversationInfoProto.CONVERSATION_FLAGS, mConversationFlags); } /** Reads from {@link ProtoInputStream} and constructs a {@link ConversationInfo}. */ @NonNull static ConversationInfo readFromProto(@NonNull ProtoInputStream protoInputStream) throws IOException { ConversationInfo.Builder builder = new ConversationInfo.Builder(); while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { switch (protoInputStream.getFieldNumber()) { case (int) ConversationInfoProto.SHORTCUT_ID: builder.setShortcutId( protoInputStream.readString(ConversationInfoProto.SHORTCUT_ID)); break; case (int) ConversationInfoProto.LOCUS_ID_PROTO: long locusIdToken = protoInputStream.start( ConversationInfoProto.LOCUS_ID_PROTO); while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { if (protoInputStream.getFieldNumber() == (int) LocusIdProto.LOCUS_ID) { builder.setLocusId(new LocusId( protoInputStream.readString(LocusIdProto.LOCUS_ID))); } } protoInputStream.end(locusIdToken); break; case (int) ConversationInfoProto.CONTACT_URI: builder.setContactUri(Uri.parse(protoInputStream.readString( ConversationInfoProto.CONTACT_URI))); break; case (int) ConversationInfoProto.NOTIFICATION_CHANNEL_ID: builder.setNotificationChannelId(protoInputStream.readString( ConversationInfoProto.NOTIFICATION_CHANNEL_ID)); break; case (int) ConversationInfoProto.SHORTCUT_FLAGS: builder.setShortcutFlags(protoInputStream.readInt( ConversationInfoProto.SHORTCUT_FLAGS)); break; case (int) ConversationInfoProto.CONVERSATION_FLAGS: builder.setConversationFlags(protoInputStream.readInt( ConversationInfoProto.CONVERSATION_FLAGS)); break; default: Slog.w(TAG, "Could not read undefined field: " + protoInputStream.getFieldNumber()); } } return builder.build(); } /** * Builder class for {@link ConversationInfo} objects. */ Loading
services/people/java/com/android/server/people/data/ConversationStore.java +227 −24 File changed.Preview size limit exceeded, changes collapsed. Show changes
services/people/java/com/android/server/people/data/DataManager.java +16 −1 Original line number Diff line number Diff line Loading @@ -86,6 +86,7 @@ public class DataManager { private final Context mContext; private final Injector mInjector; private final ScheduledExecutorService mUsageStatsQueryExecutor; private final ScheduledExecutorService mDiskReadWriterExecutor; private final SparseArray<UserData> mUserDataArray = new SparseArray<>(); private final SparseArray<BroadcastReceiver> mBroadcastReceivers = new SparseArray<>(); Loading Loading @@ -113,6 +114,7 @@ public class DataManager { BackgroundThread.getHandler()); mMmsSmsContentObserver = new MmsSmsContentObserver( BackgroundThread.getHandler()); mDiskReadWriterExecutor = mInjector.createScheduledExecutor(); } /** Initialization. Called when the system services are up running. */ Loading @@ -122,13 +124,18 @@ public class DataManager { mUserManager = mContext.getSystemService(UserManager.class); mShortcutServiceInternal.addListener(new ShortcutServiceListener()); IntentFilter shutdownIntentFilter = new IntentFilter(Intent.ACTION_SHUTDOWN); BroadcastReceiver shutdownBroadcastReceiver = new ShutdownBroadcastReceiver(); mContext.registerReceiver(shutdownBroadcastReceiver, shutdownIntentFilter); } /** This method is called when a user is unlocked. */ public void onUserUnlocked(int userId) { UserData userData = mUserDataArray.get(userId); if (userData == null) { userData = new UserData(userId); userData = new UserData(userId, mDiskReadWriterExecutor, mInjector.createContactsQueryHelper(mContext)); mUserDataArray.put(userId, userData); } userData.setUserUnlocked(); Loading Loading @@ -662,6 +669,14 @@ public class DataManager { } } private class ShutdownBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { forAllPackages(PackageData::saveToDisk); } } @VisibleForTesting static class Injector { Loading
services/people/java/com/android/server/people/data/PackageData.java +23 −2 Original line number Diff line number Diff line Loading @@ -22,6 +22,8 @@ import android.annotation.UserIdInt; import android.content.LocusId; import android.text.TextUtils; import java.io.File; import java.util.concurrent.ScheduledExecutorService; import java.util.function.Consumer; import java.util.function.Predicate; Loading @@ -43,17 +45,36 @@ public class PackageData { private final Predicate<String> mIsDefaultSmsAppPredicate; private final File mPackageDataDir; PackageData(@NonNull String packageName, @UserIdInt int userId, @NonNull Predicate<String> isDefaultDialerPredicate, @NonNull Predicate<String> isDefaultSmsAppPredicate) { @NonNull Predicate<String> isDefaultSmsAppPredicate, @NonNull ScheduledExecutorService scheduledExecutorService, @NonNull File perUserPeopleDataDir, @NonNull ContactsQueryHelper helper) { mPackageName = packageName; mUserId = userId; mConversationStore = new ConversationStore(); mPackageDataDir = new File(perUserPeopleDataDir, mPackageName); mConversationStore = new ConversationStore(mPackageDataDir, scheduledExecutorService, helper); mEventStore = new EventStore(); mIsDefaultDialerPredicate = isDefaultDialerPredicate; mIsDefaultSmsAppPredicate = isDefaultSmsAppPredicate; } /** Called when user is unlocked. */ void loadFromDisk() { mPackageDataDir.mkdirs(); mConversationStore.loadConversationsFromDisk(); } /** Called when device is shutting down. */ void saveToDisk() { mConversationStore.saveConversationsToDisk(); } @NonNull public String getPackageName() { return mPackageName; Loading