All Downloads are FREE. Search and download functionalities are using the official Maven repository.

java.lang.System Maven / Gradle / Ivy

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */
/*
 * Copyright (C) 2008 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.
 */
/*
 * Copyright (C) 2012 Trillian Mobile AB
 *
 * 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 java.lang;

import dalvik.system.VMRuntime;
import dalvik.system.VMStack;
import java.io.BufferedInputStream;
import java.io.Console;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.nio.channels.Channel;
import java.nio.channels.spi.SelectorProvider;
import java.util.AbstractMap;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import libcore.icu.ICU;
import libcore.io.ErrnoException;
import libcore.io.Libcore;
import libcore.io.StructPasswd;
import libcore.io.StructUtsname;
import libcore.util.ZoneInfoDB;

import org.robovm.rt.VM;

/**
 * Provides access to system-related information and resources including
 * standard input and output. Enables clients to dynamically load native
 * libraries. All methods of this class are accessed in a static way and the
 * class itself can not be instantiated.
 *
 * @see Runtime
 */
public final class System {

    /**
     * Default input stream.
     */
    public static final InputStream in;

    /**
     * Default output stream.
     */
    public static final PrintStream out;

    /**
     * Default error output stream.
     */
    public static final PrintStream err;

    private static final String lineSeparator;
    private static Properties systemProperties;

    static {
        err = new PrintStream(new FileOutputStream(FileDescriptor.err));
        out = new PrintStream(new FileOutputStream(FileDescriptor.out));
        in = new BufferedInputStream(new FileInputStream(FileDescriptor.in));
        lineSeparator = System.getProperty("line.separator");
    }

    /**
     * Sets the standard input stream to the given user defined input stream.
     *
     * @param newIn
     *            the user defined input stream to set as the standard input
     *            stream.
     */
    public static void setIn(InputStream newIn) {
        setFieldImpl("in", "Ljava/io/InputStream;", newIn);
    }

    /**
     * Sets the standard output stream to the given user defined output stream.
     *
     * @param newOut
     *            the user defined output stream to set as the standard output
     *            stream.
     */
    public static void setOut(PrintStream newOut) {
        setFieldImpl("out", "Ljava/io/PrintStream;", newOut);
    }

    /**
     * Sets the standard error output stream to the given user defined output
     * stream.
     *
     * @param newErr
     *            the user defined output stream to set as the standard error
     *            output stream.
     */
    public static void setErr(PrintStream newErr) {
        setFieldImpl("err", "Ljava/io/PrintStream;", newErr);
    }

    /**
     * Prevents this class from being instantiated.
     */
    private System() {
    }

    /**
     * Copies {@code length} elements from the array {@code src},
     * starting at offset {@code srcPos}, into the array {@code dst},
     * starting at offset {@code dstPos}.
     *
     * 

The source and destination arrays can be the same array, * in which case copying is performed as if the source elements * are first copied into a temporary array and then into the * destination array. * * @param src * the source array to copy the content. * @param srcPos * the starting index of the content in {@code src}. * @param dst * the destination array to copy the data into. * @param dstPos * the starting index for the copied content in {@code dst}. * @param length * the number of elements to be copied. */ public static void arraycopy(Object src, int srcPos, Object dst, int dstPos, int length) { // RoboVM note: This is native in Android. We're using code from Apache Harmony instead. if (src == null) { throw new NullPointerException("src == null"); } if (dst == null) { throw new NullPointerException("dst == null"); } Class type1 = src.getClass(); Class type2 = dst.getClass(); if (!type1.isArray()) { throw new ArrayStoreException("source of type " + type1.getName() + " is not an array"); } if (!type2.isArray()) { throw new ArrayStoreException("destination of type " + type2.getName() + " is not an array"); } Class componentType1 = type1.getComponentType(); Class componentType2 = type2.getComponentType(); if (!componentType1.isPrimitive()) { if (componentType2.isPrimitive()) { throw new ArrayStoreException(type1.getCanonicalName() + " and " + type2.getCanonicalName() + " are incompatible array types"); } arraycopy((Object[]) src, srcPos, (Object[]) dst, dstPos, length); } else { if (componentType2 != componentType1) { throw new ArrayStoreException(type1.getCanonicalName() + " and " + type2.getCanonicalName() + " are incompatible array types"); } if (componentType1 == int.class) { arraycopy((int[]) src, srcPos, (int[]) dst, dstPos, length); } else if (componentType1 == byte.class) { arraycopy((byte[]) src, srcPos, (byte[]) dst, dstPos, length); } else if (componentType1 == long.class) { arraycopy((long[]) src, srcPos, (long[]) dst, dstPos, length); } else if (componentType1 == short.class) { arraycopy((short[]) src, srcPos, (short[]) dst, dstPos, length); } else if (componentType1 == char.class) { arraycopy((char[]) src, srcPos, (char[]) dst, dstPos, length); } else if (componentType1 == boolean.class) { arraycopy((boolean[]) src, srcPos, (boolean[]) dst, dstPos, length); } else if (componentType1 == double.class) { arraycopy((double[]) src, srcPos, (double[]) dst, dstPos, length); } else if (componentType1 == float.class) { arraycopy((float[]) src, srcPos, (float[]) dst, dstPos, length); } } } private static void arraycopyCheckBounds(int srcLength, int srcPos, int dstLength, int dstPos, int length) { if (srcPos < 0 || dstPos < 0 || length < 0 || srcPos > srcLength - length || dstPos > dstLength - length) { throw new ArrayIndexOutOfBoundsException("src.length=" + srcLength + " srcPos=" + srcPos + " dst.length=" + dstLength + " dstPos=" + dstPos + " length=" + length); } } private static void arraycopyFast(Object src, int srcPos, Object dst, int dstPos, int length, int logElemSize) { if (length > 0) { long srcAddr = VM.getArrayValuesAddress(src) + (srcPos << logElemSize); long dstAddr = VM.getArrayValuesAddress(dst) + (dstPos << logElemSize); if (logElemSize == 0) { VM.memmove8(dstAddr, srcAddr, length); } else if (logElemSize == 1) { VM.memmove16(dstAddr, srcAddr, length); } else if (logElemSize == 2) { VM.memmove32(dstAddr, srcAddr, length); } else if (logElemSize == 3) { VM.memmove64(dstAddr, srcAddr, length); } else { throw new AssertionError(); } } } private static void arraycopy(Object[] src, int srcPos, Object[] dst, int dstPos, int length) { arraycopyCheckBounds(src.length, srcPos, dst.length, dstPos, length); if (length > 0) { // TODO: Use arraycopyFast() if src.class and dst.class have same dimensionality and (src instanceof dst) int i = 0; try { // Check if this is a forward or backwards arraycopy if (src != dst || srcPos > dstPos || srcPos + length <= dstPos) { for (i = 0; i < length; ++i) { dst[dstPos + i] = src[srcPos + i]; } } else { for (i = length - 1; i >= 0; --i) { dst[dstPos + i] = src[srcPos + i]; } } } catch (ArrayStoreException e) { // Throw a new one with a more descriptive message. Class srcElemClass = src[i + srcPos].getClass(); String srcElemTypeName = srcElemClass.isArray() ? srcElemClass.getCanonicalName() : srcElemClass.getName(); throw new ArrayStoreException(String.format( "source[%d] of type %s cannot be stored in destination array of type %s", i + srcPos, srcElemTypeName, dst.getClass().getCanonicalName())); } } } private static void arraycopy(int[] src, int srcPos, int[] dst, int dstPos, int length) { arraycopyCheckBounds(src.length, srcPos, dst.length, dstPos, length); arraycopyFast(src, srcPos, dst, dstPos, length, 2); } private static void arraycopy(byte[] src, int srcPos, byte[] dst, int dstPos, int length) { arraycopyCheckBounds(src.length, srcPos, dst.length, dstPos, length); arraycopyFast(src, srcPos, dst, dstPos, length, 0); } private static void arraycopy(short[] src, int srcPos, short[] dst, int dstPos, int length) { arraycopyCheckBounds(src.length, srcPos, dst.length, dstPos, length); arraycopyFast(src, srcPos, dst, dstPos, length, 1); } private static void arraycopy(long[] src, int srcPos, long[] dst, int dstPos, int length) { arraycopyCheckBounds(src.length, srcPos, dst.length, dstPos, length); arraycopyFast(src, srcPos, dst, dstPos, length, 3); } private static void arraycopy(char[] src, int srcPos, char[] dst, int dstPos, int length) { arraycopyCheckBounds(src.length, srcPos, dst.length, dstPos, length); arraycopyFast(src, srcPos, dst, dstPos, length, 1); } private static void arraycopy(boolean[] src, int srcPos, boolean[] dst, int dstPos, int length) { arraycopyCheckBounds(src.length, srcPos, dst.length, dstPos, length); arraycopyFast(src, srcPos, dst, dstPos, length, 0); } private static void arraycopy(double[] src, int srcPos, double[] dst, int dstPos, int length) { arraycopyCheckBounds(src.length, srcPos, dst.length, dstPos, length); arraycopyFast(src, srcPos, dst, dstPos, length, 3); } private static void arraycopy(float[] src, int srcPos, float[] dst, int dstPos, int length) { arraycopyCheckBounds(src.length, srcPos, dst.length, dstPos, length); arraycopyFast(src, srcPos, dst, dstPos, length, 2); } /** * Returns the current time in milliseconds since January 1, 1970 00:00:00.0 UTC. * *

This method always returns UTC times, regardless of the system's time zone. * This is often called "Unix time" or "epoch time". * Use a {@link java.text.DateFormat} instance to format this time for display to a human. * *

This method shouldn't be used for measuring timeouts or * other elapsed time measurements, as changing the system time can affect * the results. Use {@link #nanoTime} for that. */ public static native long currentTimeMillis(); /** * Returns the current timestamp of the most precise timer available on the * local system, in nanoseconds. Equivalent to Linux's {@code CLOCK_MONOTONIC}. * *

This timestamp should only be used to measure a duration by comparing it * against another timestamp on the same device. * Values returned by this method do not have a defined correspondence to * wall clock times; the zero value is typically whenever the device last booted. * Use {@link #currentTimeMillis} if you want to know what time it is. */ public static native long nanoTime(); /** * Causes the VM to stop running and the program to exit with the given exit status. * If {@link #runFinalizersOnExit(boolean)} has been previously invoked with a * {@code true} argument, then all objects will be properly * garbage-collected and finalized first. */ public static void exit(int code) { Runtime.getRuntime().exit(code); } /** * Indicates to the VM that it would be a good time to run the * garbage collector. Note that this is a hint only. There is no guarantee * that the garbage collector will actually be run. */ public static void gc() { Runtime.getRuntime().gc(); } /** * Returns the value of the environment variable with the given name, or null if no such * variable exists. */ public static String getenv(String name) { if (name == null) { throw new NullPointerException("name == null"); } return Libcore.os.getenv(name); } /** * Returns an unmodifiable map of all environment variables to their values. */ public static Map getenv() { Map map = new HashMap(); for (String entry : Libcore.os.environ()) { int index = entry.indexOf('='); if (index != -1) { map.put(entry.substring(0, index), entry.substring(index + 1)); } } return new SystemEnvironment(map); } /** * Returns the inherited channel from the creator of the current virtual * machine. * * @return the inherited {@link Channel} or {@code null} if none exists. * @throws IOException * if an I/O error occurred. * @see SelectorProvider * @see SelectorProvider#inheritedChannel() */ public static Channel inheritedChannel() throws IOException { return SelectorProvider.provider().inheritedChannel(); } /** * Returns the system properties. Note that this is not a copy, so that * changes made to the returned Properties object will be reflected in * subsequent calls to getProperty and getProperties. * * @return the system properties. */ public static Properties getProperties() { if (systemProperties == null) { initSystemProperties(); } return systemProperties; } private static void initSystemProperties() { VMRuntime runtime = VMRuntime.getRuntime(); Properties p = new Properties(); String projectUrl = "http://www.robovm.org/"; String projectName = "RoboVM"; p.put("java.boot.class.path", runtime.bootClassPath()); p.put("java.class.path", runtime.classPath()); // None of these four are meaningful on Android, but these keys are guaranteed // to be present for System.getProperty. For java.class.version, we use the maximum // class file version that dx currently supports. p.put("java.class.version", "50.0"); p.put("java.compiler", ""); p.put("java.ext.dirs", ""); p.put("java.version", "0"); // RoboVM note: Android uses getenv("JAVA_HOME") here with "/system" as fallback. p.put("java.home", VM.basePath()); // RoboVM note: Use value of $TMPDIR if set. Otherwise use /tmp as Android does. String tmpdir = getenv("TMPDIR"); p.put("java.io.tmpdir", tmpdir != null ? tmpdir : "/tmp"); String ldLibraryPath = getenv("LD_LIBRARY_PATH"); if (ldLibraryPath != null) { p.put("java.library.path", ldLibraryPath); } p.put("java.specification.name", "RoboVM Core Library"); p.put("java.specification.vendor", projectName); p.put("java.specification.version", "0.9"); p.put("java.vendor", projectName); p.put("java.vendor.url", projectUrl); p.put("java.vm.name", "RoboVM"); p.put("java.vm.specification.name", "RoboVM Virtual Machine Specification"); p.put("java.vm.specification.vendor", projectName); p.put("java.vm.specification.version", "0.9"); p.put("java.vm.vendor", projectName); p.put("java.vm.version", runtime.vmVersion()); p.put("file.separator", "/"); p.put("line.separator", "\n"); p.put("path.separator", ":"); p.put("java.runtime.name", "RoboVM Runtime"); p.put("java.runtime.version", "0.9"); p.put("java.vm.vendor.url", projectUrl); p.put("file.encoding", "UTF-8"); p.put("user.language", "en"); p.put("user.region", "US"); try { StructPasswd passwd = Libcore.os.getpwuid(Libcore.os.getuid()); p.put("user.home", passwd.pw_dir); p.put("user.name", passwd.pw_name); } catch (ErrnoException exception) { // RoboVM note: Start change. Fall back to environment variables. getpwuid() fails on iOS. String home = getenv("HOME"); String user = getenv("USER"); p.put("user.home", home != null ? home : ""); p.put("user.name", user != null ? user : ""); // RoboVM note: End change. } StructUtsname info = Libcore.os.uname(); p.put("os.arch", info.machine); p.put("os.name", info.sysname); p.put("os.version", info.release); // Undocumented Android-only properties. p.put("android.icu.library.version", ICU.getIcuVersion()); p.put("android.icu.unicode.version", ICU.getUnicodeVersion()); p.put("android.icu.cldr.version", ICU.getCldrVersion()); parsePropertyAssignments(p, specialProperties()); parsePropertyAssignments(p, robovmSpecialProperties()); // Override built-in properties with settings from the command line. parsePropertyAssignments(p, runtime.properties()); systemProperties = p; } /** * Returns an array of "key=value" strings containing information not otherwise * easily available, such as #defined library versions. */ private static native String[] specialProperties(); private static native String[] robovmSpecialProperties(); /** * Adds each element of 'assignments' to 'p', treating each element as an * assignment in the form "key=value". */ private static void parsePropertyAssignments(Properties p, String[] assignments) { for (String assignment : assignments) { int split = assignment.indexOf('='); String key = assignment.substring(0, split); String value = assignment.substring(split + 1); p.put(key, value); } } /** * Returns the value of a particular system property or {@code null} if no * such property exists. * *

The following properties are always provided by the Dalvik VM: *

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Name Meaning Example
file.separator {@link java.io.File#separator} {@code /}
java.class.path System class path {@code .}
java.class.version (Not useful on Android) {@code 50.0}
java.compiler (Not useful on Android) Empty
java.ext.dirs (Not useful on Android) Empty
java.home Location of the VM on the file system {@code /system}
java.io.tmpdir See {@link java.io.File#createTempFile} {@code /sdcard}
java.library.path Search path for JNI libraries {@code /vendor/lib:/system/lib}
java.vendor Human-readable VM vendor {@code The Android Project}
java.vendor.url URL for VM vendor's web site {@code http://www.android.com/}
java.version (Not useful on Android) {@code 0}
java.specification.version VM libraries version {@code 0.9}
java.specification.vendor VM libraries vendor {@code The Android Project}
java.specification.name VM libraries name {@code Dalvik Core Library}
java.vm.version VM implementation version {@code 1.2.0}
java.vm.vendor VM implementation vendor {@code The Android Project}
java.vm.name VM implementation name {@code Dalvik}
java.vm.specification.version VM specification version {@code 0.9}
java.vm.specification.vendor VM specification vendor {@code The Android Project}
java.vm.specification.name VM specification name {@code Dalvik Virtual Machine Specification}
line.separator The system line separator {@code \n}
os.arch OS architecture {@code armv7l}
os.name OS (kernel) name {@code Linux}
os.version OS (kernel) version {@code 2.6.32.9-g103d848}
path.separator See {@link java.io.File#pathSeparator} {@code :}
user.dir Base of non-absolute paths {@code /}
user.home (Not useful on Android) Empty
user.name (Not useful on Android) Empty
* *

It is a mistake to try to override any of these. Doing so will have unpredictable results. * * @param propertyName * the name of the system property to look up. * @return the value of the specified system property or {@code null} if the * property doesn't exist. */ public static String getProperty(String propertyName) { return getProperty(propertyName, null); } /** * Returns the value of a particular system property. The {@code * defaultValue} will be returned if no such property has been found. */ public static String getProperty(String name, String defaultValue) { checkPropertyName(name); return getProperties().getProperty(name, defaultValue); } /** * Sets the value of a particular system property. * * @return the old value of the property or {@code null} if the property * didn't exist. */ public static String setProperty(String name, String value) { checkPropertyName(name); return (String) getProperties().setProperty(name, value); } /** * Removes a specific system property. * * @return the property value or {@code null} if the property didn't exist. * @throws NullPointerException * if the argument is {@code null}. * @throws IllegalArgumentException * if the argument is empty. */ public static String clearProperty(String name) { checkPropertyName(name); return (String) getProperties().remove(name); } private static void checkPropertyName(String name) { if (name == null) { throw new NullPointerException("name == null"); } if (name.isEmpty()) { throw new IllegalArgumentException("name is empty"); } } /** * Returns the {@link java.io.Console} associated with this VM, or null. * Not all VMs will have an associated console. A console is typically only * available for programs run from the command line. * @since 1.6 */ public static Console console() { return Console.getConsole(); } /** * Returns null. Android does not use {@code SecurityManager}. This method * is only provided for source compatibility. * * @return null */ public static SecurityManager getSecurityManager() { return null; } /** * Returns an integer hash code for the parameter. The hash code returned is * the same one that would be returned by the method {@code * java.lang.Object.hashCode()}, whether or not the object's class has * overridden hashCode(). The hash code for {@code null} is {@code 0}. * * @param anObject * the object to calculate the hash code. * @return the hash code for the given object. * @see java.lang.Object#hashCode */ public static native int identityHashCode(Object anObject); /** * Returns the system's line separator. On Android, this is {@code "\n"}. The value * comes from the value of the {@code line.separator} system property when the VM * starts. Later changes to the property will not affect the value returned by this * method. * @since 1.7 */ public static String lineSeparator() { return lineSeparator; } /** * Loads and links the dynamic library that is identified through the * specified path. This method is similar to {@link #loadLibrary(String)}, * but it accepts a full path specification whereas {@code loadLibrary} just * accepts the name of the library to load. * * @param pathName * the path of the file to be loaded. */ public static void load(String pathName) { Runtime.getRuntime().load(pathName, VMStack.getCallingClassLoader()); } /** * Loads and links the library with the specified name. The mapping of the * specified library name to the full path for loading the library is * implementation-dependent. * * @param libName * the name of the library to load. * @throws UnsatisfiedLinkError * if the library could not be loaded. */ public static void loadLibrary(String libName) { Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader()); } /** * @hide internal use only */ public static void logE(String message) { log('E', message, null); } /** * @hide internal use only */ public static void logE(String message, Throwable th) { log('E', message, th); } /** * @hide internal use only */ public static void logI(String message) { log('I', message, null); } /** * @hide internal use only */ public static void logI(String message, Throwable th) { log('I', message, th); } /** * @hide internal use only */ public static void logW(String message) { log('W', message, null); } /** * @hide internal use only */ public static void logW(String message, Throwable th) { log('W', message, th); } private static native void log(char type, String message, Throwable th); /** * Provides a hint to the VM that it would be useful to attempt * to perform any outstanding object finalization. */ public static void runFinalization() { Runtime.getRuntime().runFinalization(); } /** * Ensures that, when the VM is about to exit, all objects are * finalized. Note that all finalization which occurs when the system is * exiting is performed after all running threads have been terminated. * * @param flag * the flag determines if finalization on exit is enabled. * @deprecated This method is unsafe. */ @SuppressWarnings("deprecation") @Deprecated public static void runFinalizersOnExit(boolean flag) { Runtime.runFinalizersOnExit(flag); } /** * Sets all system properties. This does not take a copy; the passed-in object is used * directly. Passing null causes the VM to reinitialize the properties to how they were * when the VM was started. */ public static void setProperties(Properties p) { systemProperties = p; } /** * Throws {@code SecurityException}. * *

Security managers do not provide a secure environment for * executing untrusted code and are unsupported on Android. Untrusted code * cannot be safely isolated within a single VM on Android, so this method * always throws a {@code SecurityException}. * * @param sm a security manager * @throws SecurityException always */ public static void setSecurityManager(SecurityManager sm) { if (sm != null) { throw new SecurityException(); } } /** * Returns the platform specific file name format for the shared library * named by the argument. * * @param userLibName * the name of the library to look up. * @return the platform specific filename for the library. */ public static String mapLibraryName(String userLibName) { if (userLibName == null) { throw new NullPointerException("userLibName == null"); } return mapLibraryName0(userLibName); } private static native String mapLibraryName0(String userLibName); /** * Sets the value of the named static field in the receiver to the passed in * argument. * * @param fieldName * the name of the field to set, one of in, out, or err * @param stream * the new value of the field */ private static native void setFieldImpl(String fieldName, String signature, Object stream); /** * The unmodifiable environment variables map. System.getenv() specifies * that this map must throw when passed non-String keys. */ static class SystemEnvironment extends AbstractMap { private final Map map; public SystemEnvironment(Map map) { this.map = Collections.unmodifiableMap(map); } @Override public Set> entrySet() { return map.entrySet(); } @Override public String get(Object key) { return map.get(toNonNullString(key)); } @Override public boolean containsKey(Object key) { return map.containsKey(toNonNullString(key)); } @Override public boolean containsValue(Object value) { return map.containsValue(toNonNullString(value)); } private String toNonNullString(Object o) { if (o == null) { throw new NullPointerException("o == null"); } return (String) o; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy