blob: 1e3e0caa1ece85ed791ca94d1a63190a6a9b4cb3 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.pm;
import android.content.Context;
import android.content.pm.EphemeralApplicationInfo;
import android.content.pm.PackageParser;
import android.content.pm.PackageUserState;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Binder;
import android.os.Environment;
import android.provider.Settings;
import android.util.AtomicFile;
import android.util.Slog;
import android.util.SparseArray;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.XmlUtils;
import libcore.io.IoUtils;
import libcore.util.EmptyArray;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
/**
* This class is a part of the package manager service that is responsible
* for managing data associated with ephemeral apps such as cached uninstalled
* ephemeral apps and ephemeral apps' cookies.
*/
class EphemeralApplicationRegistry {
private static final boolean DEBUG = false;
private static final boolean ENABLED = false;
private static final String LOG_TAG = "EphemeralAppRegistry";
private static final long DEFAULT_UNINSTALLED_EPHEMERAL_APP_CACHE_DURATION_MILLIS =
DEBUG ? 60 * 1000L /* one min */ : 30 * 24 * 60 * 60 * 1000L; /* one month */
private final static char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
private static final String EPHEMERAL_APPS_FOLDER = "ephemeral";
private static final String EPHEMERAL_APP_ICON_FILE = "icon.png";
private static final String EPHEMERAL_APP_COOKIE_FILE_PREFIX = "cookie_";
private static final String EPHEMERAL_APP_COOKIE_FILE_SIFFIX = ".dat";
private static final String EPHEMERAL_APP_METADATA_FILE = "metadata.xml";
private static final String TAG_PACKAGE = "package";
private static final String TAG_PERMS = "perms";
private static final String TAG_PERM = "perm";
private static final String ATTR_LABEL = "label";
private static final String ATTR_NAME = "name";
private static final String ATTR_GRANTED = "granted";
private final PackageManagerService mService;
@GuardedBy("mService.mPackages")
private SparseArray<List<UninstalledEphemeralAppState>> mUninstalledEphemeralApps;
public EphemeralApplicationRegistry(PackageManagerService service) {
mService = service;
}
public byte[] getEphemeralApplicationCookieLPw(String packageName, int userId) {
if (!ENABLED) {
return EmptyArray.BYTE;
}
pruneUninstalledEphemeralAppsLPw(userId);
File cookieFile = peekEphemeralCookieFile(packageName, userId);
if (cookieFile != null && cookieFile.exists()) {
try {
return IoUtils.readFileAsByteArray(cookieFile.toString());
} catch (IOException e) {
Slog.w(LOG_TAG, "Error reading cookie file: " + cookieFile);
}
}
return null;
}
public boolean setEphemeralApplicationCookieLPw(String packageName,
byte[] cookie, int userId) {
if (!ENABLED) {
return false;
}
pruneUninstalledEphemeralAppsLPw(userId);
PackageParser.Package pkg = mService.mPackages.get(packageName);
if (pkg == null) {
return false;
}
if (!isValidCookie(mService.mContext, cookie)) {
return false;
}
File appDir = getEphemeralApplicationDir(pkg.packageName, userId);
if (!appDir.exists() && !appDir.mkdirs()) {
return false;
}
File cookieFile = computeEphemeralCookieFile(pkg, userId);
if (cookieFile.exists() && !cookieFile.delete()) {
return false;
}
try (FileOutputStream fos = new FileOutputStream(cookieFile)) {
fos.write(cookie, 0, cookie.length);
} catch (IOException e) {
Slog.w(LOG_TAG, "Error writing cookie file: " + cookieFile);
return false;
}
return true;
}
public Bitmap getEphemeralApplicationIconLPw(String packageName, int userId) {
if (!ENABLED) {
return null;
}
pruneUninstalledEphemeralAppsLPw(userId);
File iconFile = new File(getEphemeralApplicationDir(packageName, userId),
EPHEMERAL_APP_ICON_FILE);
if (iconFile.exists()) {
return BitmapFactory.decodeFile(iconFile.toString());
}
return null;
}
public List<EphemeralApplicationInfo> getEphemeralApplicationsLPw(int userId) {
if (!ENABLED) {
return Collections.emptyList();
}
pruneUninstalledEphemeralAppsLPw(userId);
List<EphemeralApplicationInfo> result = getInstalledEphemeralApplicationsLPr(userId);
result.addAll(getUninstalledEphemeralApplicationsLPr(userId));
return result;
}
public void onPackageInstalledLPw(PackageParser.Package pkg) {
if (!ENABLED) {
return;
}
PackageSetting ps = (PackageSetting) pkg.mExtras;
if (ps == null) {
return;
}
for (int userId : UserManagerService.getInstance().getUserIds()) {
pruneUninstalledEphemeralAppsLPw(userId);
// Ignore not installed apps
if (mService.mPackages.get(pkg.packageName) == null || !ps.getInstalled(userId)) {
continue;
}
// Propagate permissions before removing any state
propagateEphemeralAppPermissionsIfNeeded(pkg, userId);
// Remove the in-memory state
if (mUninstalledEphemeralApps != null) {
List<UninstalledEphemeralAppState> uninstalledAppStates =
mUninstalledEphemeralApps.get(userId);
if (uninstalledAppStates != null) {
final int appCount = uninstalledAppStates.size();
for (int i = 0; i < appCount; i++) {
UninstalledEphemeralAppState uninstalledAppState =
uninstalledAppStates.get(i);
if (uninstalledAppState.mEphemeralApplicationInfo
.getPackageName().equals(pkg.packageName)) {
uninstalledAppStates.remove(i);
break;
}
}
}
}
// Remove the on-disk state except the cookie
File ephemeralAppDir = getEphemeralApplicationDir(pkg.packageName, userId);
new File(ephemeralAppDir, EPHEMERAL_APP_METADATA_FILE).delete();
new File(ephemeralAppDir, EPHEMERAL_APP_ICON_FILE).delete();
// If app signature changed - wipe the cookie
File currentCookieFile = peekEphemeralCookieFile(pkg.packageName, userId);
if (currentCookieFile == null) {
continue;
}
File expectedCookeFile = computeEphemeralCookieFile(pkg, userId);
if (!currentCookieFile.equals(expectedCookeFile)) {
Slog.i(LOG_TAG, "Signature for package " + pkg.packageName
+ " changed - dropping cookie");
currentCookieFile.delete();
}
}
}
public void onPackageUninstalledLPw(PackageParser.Package pkg) {
if (!ENABLED) {
return;
}
if (pkg == null) {
return;
}
PackageSetting ps = (PackageSetting) pkg.mExtras;
if (ps == null) {
return;
}
for (int userId : UserManagerService.getInstance().getUserIds()) {
pruneUninstalledEphemeralAppsLPw(userId);
if (mService.mPackages.get(pkg.packageName) != null && ps.getInstalled(userId)) {
continue;
}
if (pkg.applicationInfo.isEphemeralApp()) {
// Add a record for an uninstalled ephemeral app
addUninstalledEphemeralAppLPw(pkg, userId);
} else {
// Deleting an app prunes all ephemeral state such as cookie
deleteDir(getEphemeralApplicationDir(pkg.packageName, userId));
}
}
}
public void onUserRemovedLPw(int userId) {
if (!ENABLED) {
return;
}
if (mUninstalledEphemeralApps != null) {
mUninstalledEphemeralApps.remove(userId);
}
deleteDir(getEphemeralApplicationsDir(userId));
}
private void addUninstalledEphemeralAppLPw(PackageParser.Package pkg, int userId) {
EphemeralApplicationInfo uninstalledApp = createEphemeralAppInfoForPackage(pkg, userId);
if (uninstalledApp == null) {
return;
}
if (mUninstalledEphemeralApps == null) {
mUninstalledEphemeralApps = new SparseArray<>();
}
List<UninstalledEphemeralAppState> uninstalledAppStates =
mUninstalledEphemeralApps.get(userId);
if (uninstalledAppStates == null) {
uninstalledAppStates = new ArrayList<>();
mUninstalledEphemeralApps.put(userId, uninstalledAppStates);
}
UninstalledEphemeralAppState uninstalledAppState = new UninstalledEphemeralAppState(
uninstalledApp, System.currentTimeMillis());
uninstalledAppStates.add(uninstalledAppState);
writeUninstalledEphemeralAppMetadata(uninstalledApp, userId);
writeEphemeralApplicationIconLPw(pkg, userId);
}
private void writeEphemeralApplicationIconLPw(PackageParser.Package pkg, int userId) {
File appDir = getEphemeralApplicationDir(pkg.packageName, userId);
if (!appDir.exists()) {
return;
}
Drawable icon = pkg.applicationInfo.loadIcon(mService.mContext.getPackageManager());
final Bitmap bitmap;
if (icon instanceof BitmapDrawable) {
bitmap = ((BitmapDrawable) icon).getBitmap();
} else {
bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(),
icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
icon.draw(canvas);
}
File iconFile = new File(getEphemeralApplicationDir(pkg.packageName, userId),
EPHEMERAL_APP_ICON_FILE);
try (FileOutputStream out = new FileOutputStream(iconFile)) {
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
} catch (Exception e) {
Slog.e(LOG_TAG, "Error writing ephemeral app icon", e);
}
}
private void pruneUninstalledEphemeralAppsLPw(int userId) {
final long maxCacheDurationMillis = Settings.Global.getLong(
mService.mContext.getContentResolver(),
Settings.Global.UNINSTALLED_EPHEMERAL_APP_CACHE_DURATION_MILLIS,
DEFAULT_UNINSTALLED_EPHEMERAL_APP_CACHE_DURATION_MILLIS);
// Prune in-memory state
if (mUninstalledEphemeralApps != null) {
List<UninstalledEphemeralAppState> uninstalledAppStates =
mUninstalledEphemeralApps.get(userId);
if (uninstalledAppStates != null) {
final int appCount = uninstalledAppStates.size();
for (int j = appCount - 1; j >= 0; j--) {
UninstalledEphemeralAppState uninstalledAppState = uninstalledAppStates.get(j);
final long elapsedCachingMillis = System.currentTimeMillis()
- uninstalledAppState.mTimestamp;
if (elapsedCachingMillis > maxCacheDurationMillis) {
uninstalledAppStates.remove(j);
}
}
if (uninstalledAppStates.isEmpty()) {
mUninstalledEphemeralApps.remove(userId);
}
}
}
// Prune on-disk state
File ephemeralAppsDir = getEphemeralApplicationsDir(userId);
if (!ephemeralAppsDir.exists()) {
return;
}
File[] files = ephemeralAppsDir.listFiles();
if (files == null) {
return;
}
for (File ephemeralDir : files) {
if (!ephemeralDir.isDirectory()) {
continue;
}
File metadataFile = new File(ephemeralDir, EPHEMERAL_APP_METADATA_FILE);
if (!metadataFile.exists()) {
continue;
}
final long elapsedCachingMillis = System.currentTimeMillis()
- metadataFile.lastModified();
if (elapsedCachingMillis > maxCacheDurationMillis) {
deleteDir(ephemeralDir);
}
}
}
private List<EphemeralApplicationInfo> getInstalledEphemeralApplicationsLPr(int userId) {
List<EphemeralApplicationInfo> result = null;
final int packageCount = mService.mPackages.size();
for (int i = 0; i < packageCount; i++) {
PackageParser.Package pkg = mService.mPackages.valueAt(i);
if (!pkg.applicationInfo.isEphemeralApp()) {
continue;
}
EphemeralApplicationInfo info = createEphemeralAppInfoForPackage(pkg, userId);
if (info == null) {
continue;
}
if (result == null) {
result = new ArrayList<>();
}
result.add(info);
}
return result;
}
private EphemeralApplicationInfo createEphemeralAppInfoForPackage(
PackageParser.Package pkg, int userId) {
PackageSetting ps = (PackageSetting) pkg.mExtras;
if (ps == null) {
return null;
}
PackageUserState userState = ps.readUserState(userId);
if (userState == null || !userState.installed || userState.hidden) {
return null;
}
String[] requestedPermissions = new String[pkg.requestedPermissions.size()];
pkg.requestedPermissions.toArray(requestedPermissions);
Set<String> permissions = ps.getPermissionsState().getPermissions(userId);
String[] grantedPermissions = new String[permissions.size()];
permissions.toArray(grantedPermissions);
return new EphemeralApplicationInfo(pkg.applicationInfo,
requestedPermissions, grantedPermissions);
}
private List<EphemeralApplicationInfo> getUninstalledEphemeralApplicationsLPr(int userId) {
List<UninstalledEphemeralAppState> uninstalledAppStates =
getUninstalledEphemeralAppStatesLPr(userId);
if (uninstalledAppStates == null || uninstalledAppStates.isEmpty()) {
return Collections.emptyList();
}
List<EphemeralApplicationInfo> uninstalledApps = new ArrayList<>();
final int stateCount = uninstalledAppStates.size();
for (int i = 0; i < stateCount; i++) {
UninstalledEphemeralAppState uninstalledAppState = uninstalledAppStates.get(i);
uninstalledApps.add(uninstalledAppState.mEphemeralApplicationInfo);
}
return uninstalledApps;
}
private void propagateEphemeralAppPermissionsIfNeeded(PackageParser.Package pkg, int userId) {
EphemeralApplicationInfo appInfo = getOrParseUninstalledEphemeralAppInfo(pkg.packageName, userId);
if (appInfo == null) {
return;
}
if (ArrayUtils.isEmpty(appInfo.getGrantedPermissions())) {
return;
}
final long identity = Binder.clearCallingIdentity();
try {
for (String grantedPermission : appInfo.getGrantedPermissions()) {
mService.grantRuntimePermission(pkg.packageName, grantedPermission, userId);
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
private EphemeralApplicationInfo getOrParseUninstalledEphemeralAppInfo(String packageName,
int userId) {
if (mUninstalledEphemeralApps != null) {
List<UninstalledEphemeralAppState> uninstalledAppStates =
mUninstalledEphemeralApps.get(userId);
if (uninstalledAppStates != null) {
final int appCount = uninstalledAppStates.size();
for (int i = 0; i < appCount; i++) {
UninstalledEphemeralAppState uninstalledAppState = uninstalledAppStates.get(i);
if (uninstalledAppState.mEphemeralApplicationInfo
.getPackageName().equals(packageName)) {
return uninstalledAppState.mEphemeralApplicationInfo;
}
}
}
}
File metadataFile = new File(getEphemeralApplicationDir(packageName, userId),
EPHEMERAL_APP_METADATA_FILE);
UninstalledEphemeralAppState uninstalledAppState = parseMetadataFile(metadataFile);
if (uninstalledAppState == null) {
return null;
}
return uninstalledAppState.mEphemeralApplicationInfo;
}
private List<UninstalledEphemeralAppState> getUninstalledEphemeralAppStatesLPr(int userId) {
List<UninstalledEphemeralAppState> uninstalledAppStates = null;
if (mUninstalledEphemeralApps != null) {
uninstalledAppStates = mUninstalledEphemeralApps.get(userId);
if (uninstalledAppStates != null) {
return uninstalledAppStates;
}
}
File ephemeralAppsDir = getEphemeralApplicationsDir(userId);
if (ephemeralAppsDir.exists()) {
File[] files = ephemeralAppsDir.listFiles();
if (files != null) {
for (File ephemeralDir : files) {
if (!ephemeralDir.isDirectory()) {
continue;
}
File metadataFile = new File(ephemeralDir,
EPHEMERAL_APP_METADATA_FILE);
UninstalledEphemeralAppState uninstalledAppState =
parseMetadataFile(metadataFile);
if (uninstalledAppState == null) {
continue;
}
if (uninstalledAppStates == null) {
uninstalledAppStates = new ArrayList<>();
}
uninstalledAppStates.add(uninstalledAppState);
}
}
}
if (uninstalledAppStates != null) {
if (mUninstalledEphemeralApps == null) {
mUninstalledEphemeralApps = new SparseArray<>();
}
mUninstalledEphemeralApps.put(userId, uninstalledAppStates);
}
return uninstalledAppStates;
}
private static boolean isValidCookie(Context context, byte[] cookie) {
if (ArrayUtils.isEmpty(cookie)) {
return true;
}
return cookie.length <= context.getPackageManager().getEphemeralCookieMaxSizeBytes();
}
private static UninstalledEphemeralAppState parseMetadataFile(File metadataFile) {
if (!metadataFile.exists()) {
return null;
}
FileInputStream in;
try {
in = new AtomicFile(metadataFile).openRead();
} catch (FileNotFoundException fnfe) {
Slog.i(LOG_TAG, "No ephemeral metadata file");
return null;
}
final File ephemeralDir = metadataFile.getParentFile();
final long timestamp = metadataFile.lastModified();
final String packageName = ephemeralDir.getName();
try {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(in, StandardCharsets.UTF_8.name());
return new UninstalledEphemeralAppState(
parseMetadata(parser, packageName), timestamp);
} catch (XmlPullParserException | IOException e) {
throw new IllegalStateException("Failed parsing ephemeral"
+ " metadata file: " + metadataFile, e);
} finally {
IoUtils.closeQuietly(in);
}
}
private static File computeEphemeralCookieFile(PackageParser.Package pkg, int userId) {
File appDir = getEphemeralApplicationDir(pkg.packageName, userId);
String cookieFile = EPHEMERAL_APP_COOKIE_FILE_PREFIX + computePackageCertDigest(pkg)
+ EPHEMERAL_APP_COOKIE_FILE_SIFFIX;
return new File(appDir, cookieFile);
}
private static File peekEphemeralCookieFile(String packageName, int userId) {
File appDir = getEphemeralApplicationDir(packageName, userId);
if (!appDir.exists()) {
return null;
}
for (File file : appDir.listFiles()) {
if (!file.isDirectory()
&& file.getName().startsWith(EPHEMERAL_APP_COOKIE_FILE_PREFIX)
&& file.getName().endsWith(EPHEMERAL_APP_COOKIE_FILE_SIFFIX)) {
return file;
}
}
return null;
}
private static EphemeralApplicationInfo parseMetadata(XmlPullParser parser, String packageName)
throws IOException, XmlPullParserException {
final int outerDepth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
if (TAG_PACKAGE.equals(parser.getName())) {
return parsePackage(parser, packageName);
}
}
return null;
}
private static EphemeralApplicationInfo parsePackage(XmlPullParser parser, String packageName)
throws IOException, XmlPullParserException {
String label = parser.getAttributeValue(null, ATTR_LABEL);
List<String> outRequestedPermissions = new ArrayList<>();
List<String> outGrantedPermissions = new ArrayList<>();
final int outerDepth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
if (TAG_PERMS.equals(parser.getName())) {
parsePermissions(parser, outRequestedPermissions, outGrantedPermissions);
}
}
String[] requestedPermissions = new String[outRequestedPermissions.size()];
outRequestedPermissions.toArray(requestedPermissions);
String[] grantedPermissions = new String[outGrantedPermissions.size()];
outGrantedPermissions.toArray(grantedPermissions);
return new EphemeralApplicationInfo(packageName, label,
requestedPermissions, grantedPermissions);
}
private static void parsePermissions(XmlPullParser parser, List<String> outRequestedPermissions,
List<String> outGrantedPermissions) throws IOException, XmlPullParserException {
final int outerDepth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser,outerDepth)) {
if (TAG_PERM.equals(parser.getName())) {
String permission = XmlUtils.readStringAttribute(parser, ATTR_NAME);
outRequestedPermissions.add(permission);
if (XmlUtils.readBooleanAttribute(parser, ATTR_GRANTED)) {
outGrantedPermissions.add(permission);
}
}
}
}
private void writeUninstalledEphemeralAppMetadata(
EphemeralApplicationInfo ephemeralApp, int userId) {
File appDir = getEphemeralApplicationDir(ephemeralApp.getPackageName(), userId);
if (!appDir.exists() && !appDir.mkdirs()) {
return;
}
File metadataFile = new File(appDir, EPHEMERAL_APP_METADATA_FILE);
AtomicFile destination = new AtomicFile(metadataFile);
FileOutputStream out = null;
try {
out = destination.startWrite();
XmlSerializer serializer = Xml.newSerializer();
serializer.setOutput(out, StandardCharsets.UTF_8.name());
serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
serializer.startDocument(null, true);
serializer.startTag(null, TAG_PACKAGE);
serializer.attribute(null, ATTR_LABEL, ephemeralApp.loadLabel(
mService.mContext.getPackageManager()).toString());
serializer.startTag(null, TAG_PERMS);
for (String permission : ephemeralApp.getRequestedPermissions()) {
serializer.startTag(null, TAG_PERM);
serializer.attribute(null, ATTR_NAME, permission);
if (ArrayUtils.contains(ephemeralApp.getGrantedPermissions(), permission)) {
serializer.attribute(null, ATTR_GRANTED, String.valueOf(true));
}
serializer.endTag(null, TAG_PERM);
}
serializer.endTag(null, TAG_PERMS);
serializer.endTag(null, TAG_PACKAGE);
serializer.endDocument();
destination.finishWrite(out);
} catch (Throwable t) {
Slog.wtf(LOG_TAG, "Failed to write ephemeral state, restoring backup", t);
destination.failWrite(out);
} finally {
IoUtils.closeQuietly(out);
}
}
private static String computePackageCertDigest(PackageParser.Package pkg) {
MessageDigest messageDigest;
try {
messageDigest = MessageDigest.getInstance("SHA256");
} catch (NoSuchAlgorithmException e) {
/* can't happen */
return null;
}
messageDigest.update(pkg.mSignatures[0].toByteArray());
final byte[] digest = messageDigest.digest();
final int digestLength = digest.length;
final int charCount = 2 * digestLength;
final char[] chars = new char[charCount];
for (int i = 0; i < digestLength; i++) {
final int byteHex = digest[i] & 0xFF;
chars[i * 2] = HEX_ARRAY[byteHex >>> 4];
chars[i * 2 + 1] = HEX_ARRAY[byteHex & 0x0F];
}
return new String(chars);
}
private static File getEphemeralApplicationsDir(int userId) {
return new File(Environment.getUserSystemDirectory(userId),
EPHEMERAL_APPS_FOLDER);
}
private static File getEphemeralApplicationDir(String packageName, int userId) {
return new File (getEphemeralApplicationsDir(userId), packageName);
}
private static void deleteDir(File dir) {
File[] files = dir.listFiles();
if (files != null) {
for (File file : dir.listFiles()) {
deleteDir(file);
}
}
dir.delete();
}
private static final class UninstalledEphemeralAppState {
final EphemeralApplicationInfo mEphemeralApplicationInfo;
final long mTimestamp;
public UninstalledEphemeralAppState(EphemeralApplicationInfo ephemeralApp,
long timestamp) {
mEphemeralApplicationInfo = ephemeralApp;
mTimestamp = timestamp;
}
}
}